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 PyOpenSSL 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) 2010 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 | import base64 |
---|
29 | import re |
---|
30 | import traceback |
---|
31 | from ConfigParser import SafeConfigParser |
---|
32 | |
---|
33 | from OpenSSL import crypto, SSL |
---|
34 | |
---|
35 | from myproxy.utils.openssl import OpenSSLConfig, OpenSSLConfigError |
---|
36 | |
---|
37 | |
---|
38 | class CaseSensitiveConfigParser(SafeConfigParser): |
---|
39 | '''Subclass the SafeConfigParser - to preserve the original string case of |
---|
40 | config section names |
---|
41 | ''' |
---|
42 | def optionxform(self, optionstr): |
---|
43 | '''Extend SafeConfigParser.optionxform to preserve case of option names |
---|
44 | ''' |
---|
45 | return optionstr |
---|
46 | |
---|
47 | |
---|
48 | class MyProxyServerSSLCertVerification(object): |
---|
49 | """Check MyProxy server identity. If hostname doesn't match, allow match of |
---|
50 | host's Distinguished Name against MYPROXY_SERVER_DN setting""" |
---|
51 | DN_LUT = { |
---|
52 | 'commonName': 'CN', |
---|
53 | 'organisationalUnitName': 'OU', |
---|
54 | 'organisation': 'O', |
---|
55 | 'countryName': 'C', |
---|
56 | 'emailAddress': 'EMAILADDRESS', |
---|
57 | 'localityName': 'L', |
---|
58 | 'stateOrProvinceName': 'ST', |
---|
59 | 'streetAddress': 'STREET', |
---|
60 | 'domainComponent': 'DC', |
---|
61 | 'userid': 'UID' |
---|
62 | } |
---|
63 | PARSER_RE_STR = '/(%s)=' % '|'.join(DN_LUT.keys() + DN_LUT.values()) |
---|
64 | PARSER_RE = re.compile(PARSER_RE_STR) |
---|
65 | |
---|
66 | def __init__(self, |
---|
67 | myProxyServerDN=os.environ.get('MYPROXY_SERVER_DN'), |
---|
68 | cnHostPfx='host/', |
---|
69 | **kw): |
---|
70 | """Override parent class __init__ to enable setting of myProxyServerDN |
---|
71 | setting |
---|
72 | |
---|
73 | @type myProxyServerDN: string |
---|
74 | @param myProxyServerDN: Set the expected Distinguished Name of the |
---|
75 | MyProxy server to avoid errors matching hostnames. This is useful |
---|
76 | where the hostname is not fully qualified |
---|
77 | |
---|
78 | @type cnHostPfx: string |
---|
79 | @param cnHostPfx: globus host certificates are |
---|
80 | generated by default with a 'host/' prefix to the host name. Set |
---|
81 | this keyword to '' or None to override and omit the prefix""" |
---|
82 | |
---|
83 | # Allow for quoted DN |
---|
84 | myProxyServerDN = myProxyServerDN.strip('"') |
---|
85 | |
---|
86 | dnFields = self.__class__.PARSER_RE.split(myProxyServerDN) |
---|
87 | if len(dnFields) < 2: |
---|
88 | raise MyProxyClientError('Error parsing DN string: "%s"' % |
---|
89 | myProxyServerDN) |
---|
90 | |
---|
91 | self.myProxyServerDN = zip(dnFields[1::2], dnFields[2::2]) |
---|
92 | self.myProxyServerDN.sort() |
---|
93 | self.cnHostPfx = cnHostPfx |
---|
94 | |
---|
95 | |
---|
96 | def __call__(self, connection, peerCert, errorStatus, errorDepth, |
---|
97 | successStatus): |
---|
98 | """Verify MyProxy server certificate |
---|
99 | |
---|
100 | @type connection: OpenSSL.SSL.Connection |
---|
101 | @param connection: SSL connection object |
---|
102 | @type peerCert: basestring |
---|
103 | @param peerCert: MyProxy server host certificate as OpenSSL.crypto.X509 |
---|
104 | instance |
---|
105 | @type errorStatus: int |
---|
106 | @param errorStatus: error code to return if verification fails |
---|
107 | @type errorDepth: int |
---|
108 | @param errorDepth: |
---|
109 | @type successStatus: int |
---|
110 | @param successStatus: |
---|
111 | @rtype: int |
---|
112 | @return: status code |
---|
113 | """ |
---|
114 | return errorStatus |
---|
115 | |
---|
116 | if peerCert.has_expired(): |
---|
117 | # Any expired certificate in the chain should result in an error |
---|
118 | if log.getEffectiveLevel() == logging.DEBUG: |
---|
119 | log.debug('Certificate %r in peer certificate chain has ', |
---|
120 | 'expired', peerCert.get_subject()) |
---|
121 | |
---|
122 | return errorStatus |
---|
123 | |
---|
124 | elif errorDepth == 0: |
---|
125 | # Only interested in DN of last certificate in the chain - this must |
---|
126 | # match the expected MyProxy Server DN setting |
---|
127 | peerCertDN = peerCert.get_subject().get_components() |
---|
128 | peerCertDN.sort() |
---|
129 | |
---|
130 | if peerCertDN == self.myProxyServerDN: |
---|
131 | return successStatus |
---|
132 | else: |
---|
133 | return errorStatus |
---|
134 | else: |
---|
135 | return successStatus |
---|
136 | |
---|
137 | |
---|
138 | |
---|
139 | class MyProxyClientError(Exception): |
---|
140 | """Base exception class for MyProxyClient exceptions""" |
---|
141 | |
---|
142 | class MyProxyClientConfigError(MyProxyClientError): |
---|
143 | """Error with configuration""" |
---|
144 | |
---|
145 | class MyProxyClientGetError(MyProxyClientError): |
---|
146 | """Exceptions arising from get request to server""" |
---|
147 | |
---|
148 | class MyProxyClientRetrieveError(MyProxyClientError): |
---|
149 | """Error recovering a response from MyProxy""" |
---|
150 | |
---|
151 | class MyProxyCredentialsAlreadyExist(MyProxyClientError): |
---|
152 | """Attempting to upload credentials to the server which already exist. - |
---|
153 | See MyProxyClient.store |
---|
154 | """ |
---|
155 | |
---|
156 | class MyProxyClientGetTrustRootsError(MyProxyClientError): |
---|
157 | """Error retrieving trust roots""" |
---|
158 | |
---|
159 | |
---|
160 | class MyProxyClient(object): |
---|
161 | """MyProxy client interface |
---|
162 | |
---|
163 | Based on protocol definitions in: |
---|
164 | |
---|
165 | http://grid.ncsa.uiuc.edu/myproxy/protocol/ |
---|
166 | |
---|
167 | @type GET_CMD: string |
---|
168 | @cvar GET_CMD: get command string |
---|
169 | |
---|
170 | @type INFO_CMD: string |
---|
171 | @cvar INFO_CMD: info command string |
---|
172 | |
---|
173 | @type DESTROY_CMD: string |
---|
174 | @cvar DESTROY_CMD: destroy command string |
---|
175 | |
---|
176 | @type CHANGE_PASSPHRASE_CMD: string |
---|
177 | @cvar CHANGE_PASSPHRASE_CMD: command string to change cred pass-phrase |
---|
178 | |
---|
179 | @type STORE_CMD: string |
---|
180 | @cvar STORE_CMD: store command string |
---|
181 | |
---|
182 | @type GET_TRUST_ROOTS_CMD: string |
---|
183 | @cvar GET_TRUST_ROOTS_CMD: get trust roots command string |
---|
184 | |
---|
185 | @type _hostCertSubDirPath: string |
---|
186 | @cvar _hostCertSubDirPath: sub-directory path host certificate (as tuple) |
---|
187 | |
---|
188 | @type _hostKeySubDirPath: string |
---|
189 | @cvar _hostKeySubDirPath: sub-directory path to host key (as tuple) |
---|
190 | |
---|
191 | @type PRIKEY_NBITS: int |
---|
192 | @cvar PRIKEY_NBITS: default number of bits for private key generated |
---|
193 | |
---|
194 | @type MESSAGE_DIGEST_TYPE: string |
---|
195 | @cvar MESSAGE_DIGEST_TYPE: message digest type is MD5 |
---|
196 | |
---|
197 | @type SERVER_RESP_BLK_SIZE: int |
---|
198 | @cvar SERVER_RESP_BLK_SIZE: block size for retrievals from server |
---|
199 | |
---|
200 | @type MAX_RECV_TRIES: int |
---|
201 | @cvar MAX_RECV_TRIES: maximum number of retrievals of size |
---|
202 | SERVER_RESP_BLK_SIZE before this client gives up |
---|
203 | |
---|
204 | @type DEF_PROXY_FILEPATH: string |
---|
205 | @cvar DEF_PROXY_FILEPATH: default location for proxy file to be written to |
---|
206 | |
---|
207 | @type PROXY_FILE_PERMISSIONS: int |
---|
208 | @cvar PROXY_FILE_PERMISSIONS: file permissions returned proxy file is |
---|
209 | created with |
---|
210 | |
---|
211 | @type propertyDefaults: tuple |
---|
212 | @cvar propertyDefaults: sets permissable element names for MyProxy config |
---|
213 | file |
---|
214 | """ |
---|
215 | |
---|
216 | GET_CMD="""VERSION=MYPROXYv2 |
---|
217 | COMMAND=0 |
---|
218 | USERNAME=%s |
---|
219 | PASSPHRASE=%s |
---|
220 | LIFETIME=%d""" |
---|
221 | |
---|
222 | INFO_CMD="""VERSION=MYPROXYv2 |
---|
223 | COMMAND=2 |
---|
224 | USERNAME=%s |
---|
225 | PASSPHRASE=PASSPHRASE |
---|
226 | LIFETIME=0""" |
---|
227 | |
---|
228 | DESTROY_CMD="""VERSION=MYPROXYv2 |
---|
229 | COMMAND=3 |
---|
230 | USERNAME=%s |
---|
231 | PASSPHRASE=PASSPHRASE |
---|
232 | LIFETIME=0""" |
---|
233 | |
---|
234 | CHANGE_PASSPHRASE_CMD="""VERSION=MYPROXYv2 |
---|
235 | COMMAND=4 |
---|
236 | USERNAME=%s |
---|
237 | PASSPHRASE=%s |
---|
238 | NEW_PHRASE=%s |
---|
239 | LIFETIME=0""" |
---|
240 | |
---|
241 | STORE_CMD="""VERSION=MYPROXYv2 |
---|
242 | COMMAND=5 |
---|
243 | USERNAME=%s |
---|
244 | PASSPHRASE= |
---|
245 | LIFETIME=%d""" |
---|
246 | |
---|
247 | GET_TRUST_ROOTS_CMD="""VERSION=MYPROXYv2 |
---|
248 | COMMAND=7 |
---|
249 | USERNAME=%s |
---|
250 | PASSPHRASE=%s |
---|
251 | LIFETIME=0 |
---|
252 | TRUSTED_CERTS=1""" |
---|
253 | |
---|
254 | _hostCertSubDirPath = ('etc', 'hostcert.pem') |
---|
255 | _hostKeySubDirPath = ('etc', 'hostkey.pem') |
---|
256 | |
---|
257 | # Work out default location of proxy file if it exists. This is set if a |
---|
258 | # call has been made previously to logon / get-delegation |
---|
259 | DEF_PROXY_FILEPATH = sys.platform == ('win32' and 'proxy' or |
---|
260 | sys.platform in ('linux2', 'darwin') and |
---|
261 | '/tmp/x509up_u%s' % (os.getuid()) |
---|
262 | or None) |
---|
263 | |
---|
264 | PRIKEY_NBITS = 2048 #4096 |
---|
265 | MESSAGE_DIGEST_TYPE = "md5" |
---|
266 | SERVER_RESP_BLK_SIZE = 8192 |
---|
267 | MAX_RECV_TRIES = 1024 |
---|
268 | |
---|
269 | # valid configuration property keywords |
---|
270 | propertyDefaults = { |
---|
271 | 'hostname': 'localhost', |
---|
272 | 'port': 7512, |
---|
273 | 'serverDN': '', |
---|
274 | 'serverCNPrefix': 'host/', |
---|
275 | 'openSSLConfFilePath': '', |
---|
276 | 'proxyCertMaxLifetime': 43200, |
---|
277 | 'proxyCertLifetime': 43200, |
---|
278 | 'caCertFilePath': None, |
---|
279 | 'caCertDir': None |
---|
280 | } |
---|
281 | |
---|
282 | # Restrict attributes to the above properties, their equivalent |
---|
283 | # protected values + extra OpenSSL config object. |
---|
284 | __slots__ = propertyDefaults.copy() |
---|
285 | __slots__.update(dict([('_'+k, v) for k,v in propertyDefaults.items()] + |
---|
286 | [('_openSSLConfig', None), |
---|
287 | ('_cfg', None)] |
---|
288 | ) |
---|
289 | ) |
---|
290 | |
---|
291 | def __init__(self, cfgFilePath=None, **prop): |
---|
292 | """Make any initial settings for client connections to MyProxy |
---|
293 | |
---|
294 | Settings are held in a dictionary which can be set from **prop, |
---|
295 | a call to setProperties() or by passing settings in an XML file |
---|
296 | given by cfgFilePath |
---|
297 | |
---|
298 | @param cfgFilePath: set properties via a configuration file |
---|
299 | @param **prop: set properties via keywords - see |
---|
300 | propertyDefaults class variable for a list of these |
---|
301 | """ |
---|
302 | |
---|
303 | # Default settings. Nb. '_' - override property methods in order to |
---|
304 | # set defaults |
---|
305 | for opt, val in MyProxyClient.propertyDefaults.items(): |
---|
306 | setattr(self, '_'+opt, val) |
---|
307 | |
---|
308 | # Configuration file used to get default subject when generating a |
---|
309 | # new proxy certificate request |
---|
310 | self._openSSLConfig = OpenSSLConfig() |
---|
311 | |
---|
312 | # Server host name - take from environment variable if available |
---|
313 | self.hostname = os.environ.get('MYPROXY_SERVER', |
---|
314 | MyProxyClient.propertyDefaults['hostname']) |
---|
315 | |
---|
316 | # ... and port number |
---|
317 | self.port = int(os.environ.get('MYPROXY_SERVER_PORT', |
---|
318 | MyProxyClient.propertyDefaults['port'])) |
---|
319 | |
---|
320 | # Server Distinguished Name |
---|
321 | self.serverDN = os.environ.get('MYPROXY_SERVER_DN', |
---|
322 | MyProxyClient.propertyDefaults['serverDN']) |
---|
323 | |
---|
324 | # Environment variable may be quoted |
---|
325 | if self.serverDN: |
---|
326 | self.serverDN = self.serverDN.strip('"') |
---|
327 | |
---|
328 | # keyword settings |
---|
329 | for opt, val in prop.items(): |
---|
330 | setattr(self, opt, val) |
---|
331 | |
---|
332 | # If properties file is set any parameters settings in file will |
---|
333 | # override those set by input keyword |
---|
334 | if cfgFilePath is not None: |
---|
335 | self.parseConfig(cfg=cfgFilePath) |
---|
336 | |
---|
337 | |
---|
338 | def parseConfig(self, cfg, section='DEFAULT'): |
---|
339 | '''Extract parameters from _cfg config object''' |
---|
340 | |
---|
341 | if isinstance(cfg, basestring): |
---|
342 | cfgFilePath = os.path.expandvars(cfg) |
---|
343 | self._cfg = CaseSensitiveConfigParser() |
---|
344 | self._cfg.read(cfgFilePath) |
---|
345 | else: |
---|
346 | cfgFilePath = None |
---|
347 | self._cfg = cfg |
---|
348 | |
---|
349 | for key, val in self._cfg.items(section): |
---|
350 | setattr(self, key, val) |
---|
351 | |
---|
352 | # Get/Set Property methods |
---|
353 | def _getHostname(self): |
---|
354 | return self._hostname |
---|
355 | |
---|
356 | def _setHostname(self, val): |
---|
357 | if not isinstance(val, basestring): |
---|
358 | raise AttributeError("Expecting string type for hostname " |
---|
359 | "attribute") |
---|
360 | self._hostname = val |
---|
361 | |
---|
362 | hostname = property(fget=_getHostname, |
---|
363 | fset=_setHostname, |
---|
364 | doc="hostname of MyProxy server") |
---|
365 | |
---|
366 | def _getPort(self): |
---|
367 | return self._port |
---|
368 | |
---|
369 | def _setPort(self, val): |
---|
370 | if isinstance(val, basestring): |
---|
371 | self._port = int(val) |
---|
372 | elif isinstance(val, int): |
---|
373 | self._port = val |
---|
374 | else: |
---|
375 | raise AttributeError("Expecting int type for port attribute") |
---|
376 | |
---|
377 | port = property(fget=_getPort, |
---|
378 | fset=_setPort, |
---|
379 | doc="Port number for MyProxy server") |
---|
380 | |
---|
381 | def _getServerDN(self): |
---|
382 | return self._serverDN |
---|
383 | |
---|
384 | def _setServerDN(self, val): |
---|
385 | if not isinstance(val, basestring): |
---|
386 | raise AttributeError("Expecting string type for serverDN " |
---|
387 | "attribute") |
---|
388 | self._serverDN = val |
---|
389 | |
---|
390 | serverDN = property(fget=_getServerDN, |
---|
391 | fset=_setServerDN, |
---|
392 | doc="Distinguished Name for MyProxy Server " |
---|
393 | "Certificate") |
---|
394 | |
---|
395 | def _getServerCNPrefix(self): |
---|
396 | return self._serverCNPrefix |
---|
397 | |
---|
398 | def _setServerCNPrefix(self, val): |
---|
399 | if not isinstance(val, basestring): |
---|
400 | raise AttributeError("Expecting string type for serverCNPrefix " |
---|
401 | "attribute") |
---|
402 | self._serverCNPrefix = val |
---|
403 | |
---|
404 | serverCNPrefix = property(fget=_getServerCNPrefix, |
---|
405 | fset=_setServerCNPrefix, |
---|
406 | doc="Prefix if any for Server Certificate DN " |
---|
407 | "Common Name e.g. 'host/'") |
---|
408 | |
---|
409 | def _getOpenSSLConfFilePath(self): |
---|
410 | return self._openSSLConfFilePath |
---|
411 | |
---|
412 | def _setOpenSSLConfFilePath(self, val): |
---|
413 | if not isinstance(val, basestring): |
---|
414 | raise AttributeError("Expecting string type for " |
---|
415 | "openSSLConfFilePath attribute") |
---|
416 | self._openSSLConfFilePath = os.path.expandvars(val) |
---|
417 | self._openSSLConfig.filePath = self._openSSLConfFilePath |
---|
418 | self._openSSLConfig.read() |
---|
419 | |
---|
420 | openSSLConfFilePath = property(fget=_getOpenSSLConfFilePath, |
---|
421 | fset=_setOpenSSLConfFilePath, |
---|
422 | doc="file path for OpenSSL config file") |
---|
423 | |
---|
424 | def _getProxyCertMaxLifetime(self): |
---|
425 | return self._proxyCertMaxLifetime |
---|
426 | |
---|
427 | def _setProxyCertMaxLifetime(self, val): |
---|
428 | if isinstance(val, basestring): |
---|
429 | self._proxyCertMaxLifetime = int(val) |
---|
430 | |
---|
431 | elif isinstance(val, int): |
---|
432 | self._proxyCertMaxLifetime = val |
---|
433 | else: |
---|
434 | raise AttributeError("Expecting int type for proxyCertMaxLifetime " |
---|
435 | "attribute") |
---|
436 | |
---|
437 | proxyCertMaxLifetime = property(fget=_getProxyCertMaxLifetime, |
---|
438 | fset=_setProxyCertMaxLifetime, |
---|
439 | doc="Default max. lifetime allowed for " |
---|
440 | "Proxy Certificate retrieved - used " |
---|
441 | "by store method") |
---|
442 | |
---|
443 | def _getProxyCertLifetime(self): |
---|
444 | return self._proxyCertLifetime |
---|
445 | |
---|
446 | def _setProxyCertLifetime(self, val): |
---|
447 | if isinstance(val, basestring): |
---|
448 | self._proxyCertLifetime = int(val) |
---|
449 | elif isinstance(val, int): |
---|
450 | self._proxyCertLifetime = val |
---|
451 | else: |
---|
452 | raise AttributeError("Expecting int type for proxyCertLifetime " |
---|
453 | "attribute") |
---|
454 | |
---|
455 | proxyCertLifetime = property(fget=_getProxyCertLifetime, |
---|
456 | fset=_setProxyCertLifetime, |
---|
457 | doc="Default proxy cert. lifetime used in " |
---|
458 | "logon request") |
---|
459 | |
---|
460 | def _getCACertFilePath(self): |
---|
461 | return self._caCertFilePath |
---|
462 | |
---|
463 | def _setCACertFilePath(self, val): |
---|
464 | '''@type val: basestring |
---|
465 | @param val: file path for CA certificate to be used to verify |
---|
466 | MyProxy server certificate''' |
---|
467 | |
---|
468 | if isinstance(val, basestring): |
---|
469 | if val == '': |
---|
470 | self._caCertFilePath = None |
---|
471 | else: |
---|
472 | self._caCertFilePath = os.path.expandvars(val) |
---|
473 | |
---|
474 | elif isinstance(val, None): |
---|
475 | raise AttributeError("Expecting string type for caCertFilePath " |
---|
476 | "attribute") |
---|
477 | |
---|
478 | caCertFilePath = property(fget=_getCACertFilePath, |
---|
479 | fset=_setCACertFilePath, |
---|
480 | doc="CA certificate file path - MyProxy server " |
---|
481 | "certificate must validate against it and/" |
---|
482 | "or any present in caCertDir") |
---|
483 | |
---|
484 | def _getCACertDir(self): |
---|
485 | return self._caCertDir |
---|
486 | |
---|
487 | def _setCACertDir(self, val): |
---|
488 | '''Specify a directory containing PEM encoded CA certs. used for |
---|
489 | validation of MyProxy server certificate. |
---|
490 | |
---|
491 | Set to None to make OpenSSL.SSL.Context.load_verify_locations ignore |
---|
492 | this parameter |
---|
493 | |
---|
494 | @type val: basestring/None |
---|
495 | @param val: directory path''' |
---|
496 | |
---|
497 | if isinstance(val, basestring): |
---|
498 | if val == '': |
---|
499 | self._caCertDir = None |
---|
500 | else: |
---|
501 | self._caCertDir = os.path.expandvars(val) |
---|
502 | |
---|
503 | elif isinstance(val, None): |
---|
504 | self._caCertDir = val |
---|
505 | else: |
---|
506 | raise AttributeError("Expecting string or None type for caCertDir " |
---|
507 | "attribute") |
---|
508 | |
---|
509 | caCertDir = property(fget=_getCACertDir, |
---|
510 | fset=_setCACertDir, |
---|
511 | doc="directory containing PEM encoded CA " |
---|
512 | "certificates. Use along with caCertFilePath " |
---|
513 | "setting to validate MyProxy server certificate") |
---|
514 | |
---|
515 | |
---|
516 | def _getOpenSSLConfig(self): |
---|
517 | "Get OpenSSLConfig object property method" |
---|
518 | return self._openSSLConfig |
---|
519 | |
---|
520 | openSSLConfig = property(fget=_getOpenSSLConfig, |
---|
521 | doc="OpenSSLConfig object") |
---|
522 | |
---|
523 | |
---|
524 | def _initConnection(self, |
---|
525 | ownerCertFile=None, |
---|
526 | ownerKeyFile=None, |
---|
527 | ownerPassphrase=None): |
---|
528 | """Initialise connection setting up SSL context and client and |
---|
529 | server side identity checks |
---|
530 | |
---|
531 | @type ownerCertFile: basestring |
---|
532 | @param ownerCertFile: client certificate and owner of credential |
---|
533 | to be acted on. Can be a proxy cert + proxy's signing cert. Cert |
---|
534 | and private key are not necessary for getDelegation / logon calls |
---|
535 | @type ownerKeyFile: basestring |
---|
536 | @param ownerKeyFile: client private key file |
---|
537 | @type ownerPassphrase: basestring |
---|
538 | @param ownerPassphrase: pass-phrase protecting private key if set - |
---|
539 | not needed in the case of a proxy private key |
---|
540 | """ |
---|
541 | # Must be version 3 for MyProxy |
---|
542 | context = SSL.Context(SSL.SSLv3_METHOD) |
---|
543 | |
---|
544 | if self.caCertFilePath or self.caCertDir: |
---|
545 | context.load_verify_locations(self.caCertFilePath, self.caCertDir) |
---|
546 | |
---|
547 | if ownerCertFile: |
---|
548 | try: |
---|
549 | context.use_certificate_chain_file(ownerCertFile) |
---|
550 | def pwdCallback(passphraseMaxLen, |
---|
551 | promptPassphraseTwice, |
---|
552 | passphrase): |
---|
553 | if len(passphrase) > passphraseMaxLen: |
---|
554 | log.error('Passphrase length %d is greater than the ' |
---|
555 | 'maximum length allowed %d', |
---|
556 | len(passphrase), passphraseMaxLen) |
---|
557 | return '' |
---|
558 | |
---|
559 | return passphrase |
---|
560 | |
---|
561 | context.set_passwd_cb(pwdCallback, ownerPassphrase) |
---|
562 | context.use_privatekey_file(ownerKeyFile) |
---|
563 | except Exception: |
---|
564 | raise MyProxyClientConfigError("Loading certificate " |
---|
565 | "and private key for SSL " |
---|
566 | "connection [also check CA " |
---|
567 | "certificate settings]: %s" % |
---|
568 | traceback.format_exc()) |
---|
569 | |
---|
570 | # Verify peer's (MyProxy server) certificate |
---|
571 | context.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, |
---|
572 | MyProxyServerSSLCertVerification()) |
---|
573 | |
---|
574 | |
---|
575 | # Disable for compatibility with myproxy server (er, globus) |
---|
576 | # globus doesn't handle this case, apparently, and instead |
---|
577 | # chokes in proxy delegation code |
---|
578 | context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS) |
---|
579 | |
---|
580 | # connect to myproxy server |
---|
581 | conn = SSL.Connection(context, socket.socket()) |
---|
582 | |
---|
583 | # Check server host identity - if host doesn't match use explicit |
---|
584 | # 'serverDN' |
---|
585 | # host/<hostname> one |
---|
586 | # hostCheck = _HostCheck(host=self.hostname, |
---|
587 | # myProxyServerDN=self.serverDN, |
---|
588 | # cnHostPfx=self.serverCNPrefix) |
---|
589 | # conn.set_post_connection_check_callback(hostCheck) |
---|
590 | |
---|
591 | return conn |
---|
592 | |
---|
593 | def _createKeyPair(self, nBitsForKey=PRIKEY_NBITS): |
---|
594 | """Generate key pair and return as PEM encoded string |
---|
595 | @type nBitsForKey: int |
---|
596 | @param nBitsForKey: number of bits for private key generation - |
---|
597 | default is 2048 |
---|
598 | @rtype: string |
---|
599 | @return: public/private key pair |
---|
600 | """ |
---|
601 | keyPair = crypto.PKey() |
---|
602 | keyPair.generate_key(crypto.TYPE_RSA, nBitsForKey) |
---|
603 | |
---|
604 | return keyPair |
---|
605 | |
---|
606 | def _createCertReq(self, CN, keyPair, messageDigest=MESSAGE_DIGEST_TYPE): |
---|
607 | """Create a certificate request. |
---|
608 | |
---|
609 | @type CN: basestring |
---|
610 | @param CN: Common Name for certificate - effectively the same as the |
---|
611 | username for the MyProxy credential |
---|
612 | @type keyPair: string/None |
---|
613 | @param keyPair: public/private key pair |
---|
614 | @type messageDigest: basestring |
---|
615 | @param messageDigest: message digest type - default is MD5 |
---|
616 | @rtype: tuple |
---|
617 | @return certificate request PEM text and private key PEM text |
---|
618 | """ |
---|
619 | |
---|
620 | # Check all required certifcate request DN parameters are set |
---|
621 | # Create certificate request |
---|
622 | certReq = crypto.X509Req() |
---|
623 | |
---|
624 | # Create public key object |
---|
625 | certReq.set_pubkey(keyPair) |
---|
626 | |
---|
627 | # Add the public key to the request |
---|
628 | certReq.sign(keyPair, messageDigest) |
---|
629 | |
---|
630 | derCertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, |
---|
631 | certReq) |
---|
632 | |
---|
633 | return derCertReq |
---|
634 | |
---|
635 | def _deserializeResponse(self, msg, *fieldNames): |
---|
636 | """ |
---|
637 | Deserialize a MyProxy server response |
---|
638 | |
---|
639 | @param msg: string response message from MyProxy server |
---|
640 | @return: tuple of integer response and errorTxt string (if any) and all |
---|
641 | the fields parsed. fields is a list of two element, field name, field |
---|
642 | value tuples. |
---|
643 | @rtype: tuple |
---|
644 | """ |
---|
645 | lines = msg.split('\n') |
---|
646 | fields = [tuple(line.split('=', 1)) for line in lines][:-1] |
---|
647 | |
---|
648 | # get response value |
---|
649 | respCode = [int(v) for k, v in fields if k == 'RESPONSE'][0] |
---|
650 | |
---|
651 | # get error text |
---|
652 | errorTxt = os.linesep.join([v for k, v in fields if k == 'ERROR']) |
---|
653 | |
---|
654 | # Check for custom fields requested by caller to this method |
---|
655 | if fieldNames: |
---|
656 | fieldsDict = {} |
---|
657 | for k, v in fields: |
---|
658 | names = [name for name in fieldNames if k.startswith(name)] |
---|
659 | if len(names) == 0: |
---|
660 | continue |
---|
661 | else: |
---|
662 | if v.isdigit(): |
---|
663 | fieldsDict[k] = int(v) |
---|
664 | else: |
---|
665 | fieldsDict[k] = v |
---|
666 | |
---|
667 | # Return additional dict item in tuple |
---|
668 | return respCode, errorTxt, fieldsDict |
---|
669 | else: |
---|
670 | return respCode, errorTxt |
---|
671 | |
---|
672 | def _deserializeCerts(self, inputDat): |
---|
673 | """Unpack certificates returned from a get delegation call to the |
---|
674 | server |
---|
675 | |
---|
676 | @param inputDat: string containing the proxy cert and private key |
---|
677 | and signing cert all in DER format |
---|
678 | |
---|
679 | @return list containing the equivalent to the input in PEM format""" |
---|
680 | pemCerts = [] |
---|
681 | dat = inputDat |
---|
682 | |
---|
683 | while dat: |
---|
684 | # find start of cert, get length |
---|
685 | ind = dat.find('\x30\x82') |
---|
686 | if ind < 0: |
---|
687 | break |
---|
688 | |
---|
689 | len = 256*ord(dat[ind+2]) + ord(dat[ind+3]) |
---|
690 | |
---|
691 | # extract der-format cert, and convert to pem |
---|
692 | derCert = dat[ind:ind+len+4] |
---|
693 | x509Cert = crypto.load_certificate(crypto.FILETYPE_ASN1, derCert) |
---|
694 | pemCert = crypto.dump_certificate(crypto.FILETYPE_PEM, x509Cert) |
---|
695 | pemCerts.append(pemCert) |
---|
696 | |
---|
697 | # trim cert from data |
---|
698 | dat = dat[ind + len + 4:] |
---|
699 | |
---|
700 | return pemCerts |
---|
701 | |
---|
702 | |
---|
703 | @classmethod |
---|
704 | def writeProxyFile(cls,proxyCert,proxyPriKey,userX509Cert,filePath=None): |
---|
705 | """Write out proxy cert to file in the same way as myproxy-logon - |
---|
706 | proxy cert, private key, user cert. Nb. output from logon can be |
---|
707 | passed direct into this method |
---|
708 | |
---|
709 | @type proxyCert: string |
---|
710 | @param proxyCert: proxy certificate |
---|
711 | @type proxyPriKey: string |
---|
712 | @param proxyPriKey: private key for proxy |
---|
713 | @type userX509Cert: string |
---|
714 | @param userX509Cert: user certificate which issued the proxy |
---|
715 | @type filePath: string |
---|
716 | @param filePath: set to override the default filePath""" |
---|
717 | |
---|
718 | if filePath is None: |
---|
719 | filePath = MyProxyClient.DEF_PROXY_FILEPATH |
---|
720 | |
---|
721 | if filePath is None: |
---|
722 | MyProxyClientConfigError("Error setting proxy file path - invalid " |
---|
723 | "platform?") |
---|
724 | |
---|
725 | outStr = proxyCert + proxyPriKey + userX509Cert |
---|
726 | open(MyProxyClient.DEF_PROXY_FILEPATH, 'w').write(outStr) |
---|
727 | try: |
---|
728 | # Make sure permissions are set correctly |
---|
729 | os.chmod(MyProxyClient.DEF_PROXY_FILEPATH, |
---|
730 | MyProxyClient.PROXY_FILE_PERMISSIONS) |
---|
731 | except Exception, e: |
---|
732 | # Don't leave the file lying around if couldn't change it's |
---|
733 | # permissions |
---|
734 | os.unlink(MyProxyClient.DEF_PROXY_FILEPATH) |
---|
735 | |
---|
736 | log.error('Unable to set %o permissions for proxy file "%s": %s'% |
---|
737 | (MyProxyClient.PROXY_FILE_PERMISSIONS, |
---|
738 | MyProxyClient.DEF_PROXY_FILEPATH, e)) |
---|
739 | raise |
---|
740 | |
---|
741 | @classmethod |
---|
742 | def readProxyFile(cls, filePath=None): |
---|
743 | """Read proxy cert file following the format used by myproxy-logon - |
---|
744 | proxy, cert, private key, user cert. |
---|
745 | |
---|
746 | @rtype: tuple |
---|
747 | @return: tuple containing proxy cert, private key, user cert""" |
---|
748 | if filePath is None: |
---|
749 | filePath = MyProxyClient.DEF_PROXY_FILEPATH |
---|
750 | |
---|
751 | if filePath is None: |
---|
752 | MyProxyClientConfigError("Error setting proxy file path - invalid " |
---|
753 | "platform?") |
---|
754 | |
---|
755 | proxy = open(MyProxyClient.DEF_PROXY_FILEPATH).read() |
---|
756 | |
---|
757 | # Split certs and key into separate tuple items |
---|
758 | return tuple(['-----BEGIN'+i for i in proxy.split('-----BEGIN')[1:]]) |
---|
759 | |
---|
760 | |
---|
761 | def info(self, |
---|
762 | username, |
---|
763 | ownerCertFile=None, |
---|
764 | ownerKeyFile=None, |
---|
765 | ownerPassphrase=None): |
---|
766 | """return True/False whether credentials exist on the server for a |
---|
767 | given username |
---|
768 | |
---|
769 | @raise MyProxyClientGetError: |
---|
770 | @raise MyProxyClientRetrieveError: |
---|
771 | |
---|
772 | @type username: string |
---|
773 | @param username: username selected for credential |
---|
774 | @type ownerCertFile: string |
---|
775 | @param ownerCertFile: certificate used for client authentication with |
---|
776 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
777 | of the stored credentials. Only the owner can later remove |
---|
778 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
779 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
780 | @type ownerKeyFile: string |
---|
781 | @param ownerKeyFile: corresponding private key file. See explanation |
---|
782 | for ownerCertFile |
---|
783 | @type ownerPassphrase: string |
---|
784 | @param ownerPassphrase: passphrase for ownerKeyFile. Omit if the |
---|
785 | private key is not password protected. |
---|
786 | """ |
---|
787 | globusLoc = os.environ.get('GLOBUS_LOCATION') |
---|
788 | if not ownerCertFile: |
---|
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 info command - ensure conversion from unicode before writing |
---|
809 | cmd = MyProxyClient.INFO_CMD % username |
---|
810 | conn.write(str(cmd)) |
---|
811 | |
---|
812 | # process server response |
---|
813 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
814 | |
---|
815 | # Pass in the names of fields to return in the dictionary 'field' |
---|
816 | respCode, errorTxt, field = self._deserializeResponse(dat, |
---|
817 | 'CRED_START_TIME', |
---|
818 | 'CRED_END_TIME', |
---|
819 | 'CRED_OWNER') |
---|
820 | |
---|
821 | return not bool(respCode), errorTxt, field |
---|
822 | |
---|
823 | |
---|
824 | def changePassphrase(self, |
---|
825 | username, |
---|
826 | passphrase, |
---|
827 | newPassphrase, |
---|
828 | ownerCertFile=None, |
---|
829 | ownerKeyFile=None, |
---|
830 | ownerPassphrase=None): |
---|
831 | """change pass-phrase protecting the credentials for a given username |
---|
832 | |
---|
833 | @raise MyProxyClientGetError: |
---|
834 | @raise MyProxyClientRetrieveError: |
---|
835 | |
---|
836 | @param username: username of credential |
---|
837 | @param passphrase: existing pass-phrase for credential |
---|
838 | @param newPassphrase: new pass-phrase to replace the existing one. |
---|
839 | @param ownerCertFile: certificate used for client authentication with |
---|
840 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
841 | of the stored credentials. Only the owner can later remove |
---|
842 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
843 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
844 | @param ownerKeyFile: corresponding private key file. See explanation |
---|
845 | for ownerCertFile |
---|
846 | @param ownerPassphrase: passphrase for ownerKeyFile. Omit if the |
---|
847 | private key is not password protected. |
---|
848 | @return none |
---|
849 | """ |
---|
850 | globusLoc = os.environ.get('GLOBUS_LOCATION') |
---|
851 | if not ownerCertFile or not ownerKeyFile: |
---|
852 | if globusLoc: |
---|
853 | ownerCertFile = os.path.join(globusLoc, |
---|
854 | *MyProxyClient._hostCertSubDirPath) |
---|
855 | ownerKeyFile = os.path.join(globusLoc, |
---|
856 | *MyProxyClient._hostKeySubDirPath) |
---|
857 | else: |
---|
858 | raise MyProxyClientError( |
---|
859 | "No client authentication cert. and private key file were given") |
---|
860 | |
---|
861 | # Set-up SSL connection |
---|
862 | conn = self._initConnection(ownerCertFile=ownerCertFile, |
---|
863 | ownerKeyFile=ownerKeyFile, |
---|
864 | ownerPassphrase=ownerPassphrase) |
---|
865 | |
---|
866 | conn.connect((self.hostname, self.port)) |
---|
867 | |
---|
868 | # send globus compatibility stuff |
---|
869 | conn.write('0') |
---|
870 | |
---|
871 | # send command - ensure conversion from unicode before writing |
---|
872 | cmd = MyProxyClient.CHANGE_PASSPHRASE_CMD % (username, |
---|
873 | passphrase, |
---|
874 | newPassphrase) |
---|
875 | conn.write(str(cmd)) |
---|
876 | |
---|
877 | # process server response |
---|
878 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
879 | |
---|
880 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
881 | if respCode: |
---|
882 | raise MyProxyClientGetError(errorTxt) |
---|
883 | |
---|
884 | |
---|
885 | def destroy(self, |
---|
886 | username, |
---|
887 | ownerCertFile=None, |
---|
888 | ownerKeyFile=None, |
---|
889 | ownerPassphrase=None): |
---|
890 | """destroy credentials from the server for a given username |
---|
891 | |
---|
892 | @raise MyProxyClientGetError: |
---|
893 | @raise MyProxyClientRetrieveError: |
---|
894 | |
---|
895 | @param username: username selected for credential |
---|
896 | @param ownerCertFile: certificate used for client authentication with |
---|
897 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
898 | of the stored credentials. Only the owner can later remove |
---|
899 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
900 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
901 | @param ownerKeyFile: corresponding private key file. See explanation |
---|
902 | for ownerCertFile |
---|
903 | @param ownerPassphrase: passphrase for ownerKeyFile. Omit if the |
---|
904 | private key is not password protected. |
---|
905 | @return none |
---|
906 | """ |
---|
907 | globusLoc = os.environ.get('GLOBUS_LOCATION') |
---|
908 | if not ownerCertFile or not ownerKeyFile: |
---|
909 | if globusLoc: |
---|
910 | ownerCertFile = os.path.join(globusLoc, |
---|
911 | *MyProxyClient._hostCertSubDirPath) |
---|
912 | ownerKeyFile = os.path.join(globusLoc, |
---|
913 | *MyProxyClient._hostKeySubDirPath) |
---|
914 | else: |
---|
915 | raise MyProxyClientError( |
---|
916 | "No client authentication cert. and private key file were given") |
---|
917 | |
---|
918 | # Set-up SSL connection |
---|
919 | conn = self._initConnection(ownerCertFile=ownerCertFile, |
---|
920 | ownerKeyFile=ownerKeyFile, |
---|
921 | ownerPassphrase=ownerPassphrase) |
---|
922 | |
---|
923 | conn.connect((self.hostname, self.port)) |
---|
924 | |
---|
925 | # send globus compatibility stuff |
---|
926 | conn.write('0') |
---|
927 | |
---|
928 | # send destroy command - ensure conversion from unicode before writing |
---|
929 | cmd = MyProxyClient.DESTROY_CMD % username |
---|
930 | conn.write(str(cmd)) |
---|
931 | |
---|
932 | # process server response |
---|
933 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
934 | |
---|
935 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
936 | if respCode: |
---|
937 | raise MyProxyClientGetError(errorTxt) |
---|
938 | |
---|
939 | |
---|
940 | def store(self, |
---|
941 | username, |
---|
942 | passphrase, |
---|
943 | certFile, |
---|
944 | keyFile, |
---|
945 | ownerCertFile=None, |
---|
946 | ownerKeyFile=None, |
---|
947 | ownerPassphrase=None, |
---|
948 | lifetime=None, |
---|
949 | force=True): |
---|
950 | """Upload credentials to the server |
---|
951 | |
---|
952 | @raise MyProxyClientGetError: |
---|
953 | @raise MyProxyClientRetrieveError: |
---|
954 | |
---|
955 | @type username: string |
---|
956 | @param username: username selected for new credential |
---|
957 | @type passphrase: string |
---|
958 | @param passphrase: pass-phrase for new credential. This is the pass |
---|
959 | phrase which protects keyfile. |
---|
960 | @type certFile: string |
---|
961 | @param certFile: user's X.509 certificate in PEM format |
---|
962 | @type keyFile: string |
---|
963 | @param keyFile: equivalent private key file in PEM format |
---|
964 | @type ownerCertFile: string |
---|
965 | @param ownerCertFile: certificate used for client authentication with |
---|
966 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
967 | of the stored credentials. Only the owner can later remove |
---|
968 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
969 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this |
---|
970 | is not set, certFile |
---|
971 | @type ownerKeyFile: string |
---|
972 | @param ownerKeyFile: corresponding private key file. See explanation |
---|
973 | for ownerCertFile |
---|
974 | @type ownerPassphrase: string |
---|
975 | @param ownerPassphrase: passphrase for ownerKeyFile. Omit if the |
---|
976 | private key is not password protected. Nb. keyFile is expected to |
---|
977 | be passphrase protected as this will be the passphrase used for |
---|
978 | logon / getDelegation. |
---|
979 | @type Force: bool |
---|
980 | @param force: set to True to overwrite any existing creds with the |
---|
981 | same username. If, force=False a check is made with a call to info. |
---|
982 | If creds already, exist exit without proceeding |
---|
983 | """ |
---|
984 | |
---|
985 | lifetime = lifetime or self.proxyCertMaxLifetime |
---|
986 | |
---|
987 | # Inputs must be string type otherwise server will reject the request |
---|
988 | if isinstance(username, unicode): |
---|
989 | username = str(username) |
---|
990 | |
---|
991 | if isinstance(passphrase, unicode): |
---|
992 | passphrase = str(passphrase) |
---|
993 | |
---|
994 | globusLoc = os.environ.get('GLOBUS_LOCATION') |
---|
995 | if not ownerCertFile or not ownerKeyFile: |
---|
996 | if globusLoc: |
---|
997 | ownerCertFile = os.path.join(globusLoc, |
---|
998 | *MyProxyClient._hostCertSubDirPath) |
---|
999 | ownerKeyFile = os.path.join(globusLoc, |
---|
1000 | *MyProxyClient._hostKeySubDirPath) |
---|
1001 | else: |
---|
1002 | # Default so that the owner is the same as the ID of the |
---|
1003 | # credentials to be uploaded. |
---|
1004 | ownerCertFile = certFile |
---|
1005 | ownerKeyFile = keyFile |
---|
1006 | ownerPassphrase = passphrase |
---|
1007 | |
---|
1008 | if not force: |
---|
1009 | # Check credentials don't already exist |
---|
1010 | if self.info(username, |
---|
1011 | ownerCertFile=ownerCertFile, |
---|
1012 | ownerKeyFile=ownerKeyFile, |
---|
1013 | ownerPassphrase=ownerPassphrase)[0]: |
---|
1014 | raise MyProxyCredentialsAlreadyExist( |
---|
1015 | "Credentials already exist for user: %s" % username) |
---|
1016 | |
---|
1017 | # Set up SSL connection |
---|
1018 | conn = self._initConnection(ownerCertFile=ownerCertFile, |
---|
1019 | ownerKeyFile=ownerKeyFile, |
---|
1020 | ownerPassphrase=ownerPassphrase) |
---|
1021 | |
---|
1022 | conn.connect((self.hostname, self.port)) |
---|
1023 | |
---|
1024 | # send globus compatibility stuff |
---|
1025 | conn.write('0') |
---|
1026 | |
---|
1027 | # send store command - ensure conversion from unicode before writing |
---|
1028 | cmd = MyProxyClient.STORE_CMD % (username, lifetime) |
---|
1029 | conn.write(str(cmd)) |
---|
1030 | |
---|
1031 | # process server response |
---|
1032 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1033 | |
---|
1034 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
1035 | if respCode: |
---|
1036 | raise MyProxyClientGetError(errorTxt) |
---|
1037 | |
---|
1038 | # Send certificate and private key |
---|
1039 | certTxt = open(certFile).read() |
---|
1040 | keyTxt = open(keyFile).read() |
---|
1041 | |
---|
1042 | conn.send(certTxt + keyTxt) |
---|
1043 | |
---|
1044 | |
---|
1045 | # process server response |
---|
1046 | resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1047 | respCode, errorTxt = self._deserializeResponse(resp) |
---|
1048 | if respCode: |
---|
1049 | raise MyProxyClientRetrieveError(errorTxt) |
---|
1050 | |
---|
1051 | |
---|
1052 | def logon(self, username, passphrase, lifetime=None, keyPair=None, |
---|
1053 | certReq=None, nBitsForKey=PRIKEY_NBITS): |
---|
1054 | """Retrieve a proxy credential from a MyProxy server |
---|
1055 | |
---|
1056 | Exceptions: MyProxyClientGetError, MyProxyClientRetrieveError |
---|
1057 | |
---|
1058 | @type username: basestring |
---|
1059 | @param username: username of credential |
---|
1060 | |
---|
1061 | @type passphrase: basestring |
---|
1062 | @param passphrase: pass-phrase for private key of credential held on |
---|
1063 | server |
---|
1064 | |
---|
1065 | @type lifetime: int |
---|
1066 | @param lifetime: lifetime for generated certificate |
---|
1067 | |
---|
1068 | @rtype: tuple |
---|
1069 | @return credentials as strings in PEM format: the |
---|
1070 | user certificate, it's private key and the issuing certificate. The |
---|
1071 | issuing certificate is only set if the user certificate is a proxy |
---|
1072 | """ |
---|
1073 | |
---|
1074 | lifetime = lifetime or self.proxyCertLifetime |
---|
1075 | |
---|
1076 | # Certificate request may be passed as an input but if not generate it |
---|
1077 | # here request here |
---|
1078 | if certReq is None: |
---|
1079 | # If no key pair was passed, generate here |
---|
1080 | if keyPair is None: |
---|
1081 | keyPair = self._createKeyPair(nBitsForKey=nBitsForKey) |
---|
1082 | |
---|
1083 | certReq = self._createCertReq(username, keyPair) |
---|
1084 | |
---|
1085 | if keyPair is not None: |
---|
1086 | pemKeyPair = crypto.dump_privatekey(crypto.FILETYPE_PEM, keyPair) |
---|
1087 | |
---|
1088 | # Set-up SSL connection |
---|
1089 | conn = self._initConnection() |
---|
1090 | conn.connect((self.hostname, self.port)) |
---|
1091 | |
---|
1092 | # send globus compatibility stuff |
---|
1093 | conn.write('0') |
---|
1094 | |
---|
1095 | # send get command - ensure conversion from unicode before writing |
---|
1096 | cmd = MyProxyClient.GET_CMD % (username, passphrase, lifetime) |
---|
1097 | conn.write(str(cmd)) |
---|
1098 | |
---|
1099 | # process server response |
---|
1100 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1101 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
1102 | if respCode: |
---|
1103 | raise MyProxyClientGetError(errorTxt) |
---|
1104 | |
---|
1105 | # Send certificate request |
---|
1106 | conn.send(certReq) |
---|
1107 | |
---|
1108 | # process certificates |
---|
1109 | # - 1st byte , number of certs |
---|
1110 | dat = conn.recv(1) |
---|
1111 | nCerts = ord(dat[0]) |
---|
1112 | |
---|
1113 | # - n certs |
---|
1114 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1115 | |
---|
1116 | # process server response |
---|
1117 | resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1118 | respCode, errorTxt = self._deserializeResponse(resp) |
---|
1119 | if respCode: |
---|
1120 | raise MyProxyClientRetrieveError(errorTxt) |
---|
1121 | |
---|
1122 | # deserialize certs from received cert data |
---|
1123 | pemCerts = self._deserializeCerts(dat) |
---|
1124 | if len(pemCerts) != nCerts: |
---|
1125 | MyProxyClientRetrieveError("%d certs expected, %d received" % |
---|
1126 | (nCerts, len(pemCerts))) |
---|
1127 | |
---|
1128 | if keyPair is not None: |
---|
1129 | # Return certs and private key |
---|
1130 | # - proxy or dynamically issued certificate (MyProxy CA mode) |
---|
1131 | # - private key |
---|
1132 | # - rest of cert chain if proxy cert issued |
---|
1133 | creds = [pemCerts[0], pemKeyPair] |
---|
1134 | creds.extend(pemCerts[1:]) |
---|
1135 | else: |
---|
1136 | # Key generated externally - return certificate chain only |
---|
1137 | creds = pemCerts |
---|
1138 | |
---|
1139 | |
---|
1140 | return tuple(creds) |
---|
1141 | |
---|
1142 | def getDelegation(self, *arg, **kw): |
---|
1143 | """Retrieve proxy cert for user - same as logon""" |
---|
1144 | return self.logon(*arg, **kw) |
---|
1145 | |
---|
1146 | def getTrustRoots(self, username='', passphrase=''): |
---|
1147 | """Get trust roots for the given MyProxy server |
---|
1148 | |
---|
1149 | @type username: basestring |
---|
1150 | @param username: username (optional) |
---|
1151 | |
---|
1152 | @type passphrase: basestring |
---|
1153 | @param passphrase: pass-phrase (optional) |
---|
1154 | server |
---|
1155 | |
---|
1156 | @return: trust root files as a dictionary keyed by file name with each |
---|
1157 | item value set to the file contents |
---|
1158 | @rtype: dict |
---|
1159 | """ |
---|
1160 | # Set-up SSL connection |
---|
1161 | conn = self._initConnection() |
---|
1162 | conn.connect((self.hostname, self.port)) |
---|
1163 | |
---|
1164 | # send globus compatibility stuff |
---|
1165 | conn.write('0') |
---|
1166 | |
---|
1167 | # send get command - ensure conversion from unicode before writing |
---|
1168 | cmd = MyProxyClient.GET_TRUST_ROOTS_CMD % (username, passphrase) |
---|
1169 | conn.write(str(cmd)) |
---|
1170 | |
---|
1171 | # process server response chunks until all consumed |
---|
1172 | dat = '' |
---|
1173 | try: |
---|
1174 | for tries in range(MyProxyClient.MAX_RECV_TRIES): |
---|
1175 | dat += conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1176 | except SSL.SysCallError: |
---|
1177 | # Expect this exception when response content exhausted |
---|
1178 | pass |
---|
1179 | |
---|
1180 | # Precaution |
---|
1181 | if tries == MyProxyClient.MAX_RECV_TRIES: |
---|
1182 | log.warning('Maximum %d tries reached for getTrustRoots response ' |
---|
1183 | 'block retrieval with block size %d', |
---|
1184 | MyProxyClient.MAX_RECV_TRIES, |
---|
1185 | MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1186 | |
---|
1187 | respCode, errorTxt, fileData = self._deserializeResponse(dat, |
---|
1188 | 'TRUSTED_CERTS', |
---|
1189 | 'FILEDATA_') |
---|
1190 | if respCode: |
---|
1191 | raise MyProxyClientGetTrustRootsError(errorTxt) |
---|
1192 | |
---|
1193 | filesDict = dict([(k, base64.b64decode(v)) |
---|
1194 | for k, v in fileData.items() if k != 'TRUSTED_CERTS']) |
---|
1195 | |
---|
1196 | return filesDict |
---|
1197 | |
---|