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