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 | import errno |
---|
32 | |
---|
33 | from OpenSSL import crypto, SSL |
---|
34 | |
---|
35 | from myproxy.utils.openssl import OpenSSLConfig |
---|
36 | from myproxy.utils import CaseSensitiveConfigParser |
---|
37 | |
---|
38 | |
---|
39 | class MyProxyServerSSLCertVerification(object): |
---|
40 | """Check MyProxy server identity. If hostname doesn't match, allow match of |
---|
41 | host's Distinguished Name against MYPROXY_SERVER_DN setting""" |
---|
42 | DN_LUT = { |
---|
43 | 'commonName': 'CN', |
---|
44 | 'organisationalUnitName': 'OU', |
---|
45 | 'organisation': 'O', |
---|
46 | 'countryName': 'C', |
---|
47 | 'emailAddress': 'EMAILADDRESS', |
---|
48 | 'localityName': 'L', |
---|
49 | 'stateOrProvinceName': 'ST', |
---|
50 | 'streetAddress': 'STREET', |
---|
51 | 'domainComponent': 'DC', |
---|
52 | 'userid': 'UID' |
---|
53 | } |
---|
54 | PARSER_RE_STR = '/(%s)=' % '|'.join(DN_LUT.keys() + DN_LUT.values()) |
---|
55 | PARSER_RE = re.compile(PARSER_RE_STR) |
---|
56 | |
---|
57 | SERVER_CN_PREFIX = 'host/' |
---|
58 | |
---|
59 | __slots__ = ('__hostname', '__cnPrefix', '__certDN') |
---|
60 | |
---|
61 | def __init__(self, |
---|
62 | certDN=None, |
---|
63 | hostname=None, |
---|
64 | cnPrefix=SERVER_CN_PREFIX): |
---|
65 | """Override parent class __init__ to enable setting of certDN |
---|
66 | setting |
---|
67 | |
---|
68 | @type certDN: string |
---|
69 | @param certDN: Set the expected Distinguished Name of the |
---|
70 | MyProxy server to avoid errors matching hostnames. This is useful |
---|
71 | where the hostname is not fully qualified |
---|
72 | """ |
---|
73 | self.__cnPrefix = None |
---|
74 | self.__certDN = None |
---|
75 | self.__hostname = None |
---|
76 | |
---|
77 | if certDN is not None: |
---|
78 | self.certDN = certDN |
---|
79 | |
---|
80 | if hostname is not None: |
---|
81 | self.hostname = hostname |
---|
82 | |
---|
83 | self.cnPrefix = cnPrefix |
---|
84 | |
---|
85 | def __call__(self, connection, peerCert, errorStatus, errorDepth, |
---|
86 | successStatus): |
---|
87 | """Verify MyProxy server certificate |
---|
88 | |
---|
89 | @type connection: OpenSSL.SSL.Connection |
---|
90 | @param connection: SSL connection object |
---|
91 | @type peerCert: basestring |
---|
92 | @param peerCert: MyProxy server host certificate as OpenSSL.crypto.X509 |
---|
93 | instance |
---|
94 | @type errorStatus: int |
---|
95 | @param errorStatus: error code to return if verification fails |
---|
96 | @type errorDepth: int |
---|
97 | @param errorDepth: |
---|
98 | @type successStatus: int |
---|
99 | @param successStatus: |
---|
100 | @rtype: int |
---|
101 | @return: status code |
---|
102 | """ |
---|
103 | if peerCert.has_expired(): |
---|
104 | # Any expired certificate in the chain should result in an error |
---|
105 | log.error('Certificate %r in peer certificate chain has expired', |
---|
106 | peerCert.get_subject()) |
---|
107 | |
---|
108 | return False |
---|
109 | |
---|
110 | elif errorDepth == 0: |
---|
111 | # Only interested in DN of last certificate in the chain - this must |
---|
112 | # match the expected MyProxy Server DN setting |
---|
113 | peerCertSubj = peerCert.get_subject() |
---|
114 | peerCertDN = peerCertSubj.get_components() |
---|
115 | peerCertDN.sort() |
---|
116 | |
---|
117 | if self.certDN is None: |
---|
118 | # Check hostname against peer certificate CN field instead: |
---|
119 | if self.hostname is None: |
---|
120 | log.error('No "hostname" or "certDN" set to check peer ' |
---|
121 | 'certificate against') |
---|
122 | return False |
---|
123 | |
---|
124 | cn = self.cnPrefix + self.hostname |
---|
125 | if peerCertSubj.commonName == cn: |
---|
126 | return True |
---|
127 | else: |
---|
128 | log.error('Peer certificate CN %r doesn\'t match the ' |
---|
129 | 'expected CN %r', peerCertSubj.commonName, cn) |
---|
130 | return False |
---|
131 | else: |
---|
132 | if peerCertDN == self.certDN: |
---|
133 | return True |
---|
134 | else: |
---|
135 | log.error('Peer certificate DN %r doesn\'t match the ' |
---|
136 | 'expected DN %r', peerCertDN, self.certDN) |
---|
137 | return False |
---|
138 | else: |
---|
139 | return True |
---|
140 | |
---|
141 | def _getCertDN(self): |
---|
142 | return self.__certDN |
---|
143 | |
---|
144 | def _setCertDN(self, val): |
---|
145 | if isinstance(val, basestring): |
---|
146 | # Allow for quoted DN |
---|
147 | certDN = val.strip('"') |
---|
148 | |
---|
149 | dnFields = self.__class__.PARSER_RE.split(certDN) |
---|
150 | if len(dnFields) < 2: |
---|
151 | raise TypeError('Error parsing DN string: "%s"' % certDN) |
---|
152 | |
---|
153 | self.__certDN = zip(dnFields[1::2], dnFields[2::2]) |
---|
154 | self.__certDN.sort() |
---|
155 | |
---|
156 | elif not isinstance(val, list): |
---|
157 | for i in val: |
---|
158 | if not len(i) == 2: |
---|
159 | raise TypeError('Expecting list of two element DN field, ' |
---|
160 | 'DN field value pairs for "certDN" ' |
---|
161 | 'attribute') |
---|
162 | self.__certDN = val |
---|
163 | else: |
---|
164 | raise TypeError('Expecting list or string type for "certDN" ' |
---|
165 | 'attribute') |
---|
166 | |
---|
167 | certDN = property(fget=_getCertDN, |
---|
168 | fset=_setCertDN, |
---|
169 | doc="Distinguished Name for MyProxy Server Certificate") |
---|
170 | |
---|
171 | # Get/Set Property methods |
---|
172 | def _getHostname(self): |
---|
173 | return self.__hostname |
---|
174 | |
---|
175 | def _setHostname(self, val): |
---|
176 | if not isinstance(val, basestring): |
---|
177 | raise TypeError("Expecting string type for hostname " |
---|
178 | "attribute") |
---|
179 | self.__hostname = val |
---|
180 | |
---|
181 | hostname = property(fget=_getHostname, |
---|
182 | fset=_setHostname, |
---|
183 | doc="hostname of MyProxy server") |
---|
184 | |
---|
185 | def _getCNPrefix(self): |
---|
186 | """References SSL Certificate verification object property!""" |
---|
187 | return self.__cnPrefix |
---|
188 | |
---|
189 | def _setCNPrefix(self, val): |
---|
190 | """Sets SSL Certificate verification object property!""" |
---|
191 | if not isinstance(val, basestring): |
---|
192 | raise TypeError("Expecting string type for hostname " |
---|
193 | "attribute") |
---|
194 | self.__cnPrefix = val |
---|
195 | |
---|
196 | cnPrefix = property(fget=_getCNPrefix, |
---|
197 | fset=_setCNPrefix, |
---|
198 | doc="Prefix for MyProxy Server Certificate " |
---|
199 | "Distinguished Name CommonName field; usually set " |
---|
200 | "to 'host/' for Globus host certificates") |
---|
201 | |
---|
202 | |
---|
203 | class MyProxyClientError(Exception): |
---|
204 | """Base exception class for MyProxyClient exceptions""" |
---|
205 | |
---|
206 | |
---|
207 | class MyProxyClientConfigError(MyProxyClientError): |
---|
208 | """Error with configuration""" |
---|
209 | |
---|
210 | |
---|
211 | class MyProxyClientGetError(MyProxyClientError): |
---|
212 | """Exceptions arising from get request to server""" |
---|
213 | |
---|
214 | |
---|
215 | class MyProxyClientRetrieveError(MyProxyClientError): |
---|
216 | """Error recovering a response from MyProxy""" |
---|
217 | |
---|
218 | |
---|
219 | class MyProxyCredentialsAlreadyExist(MyProxyClientError): |
---|
220 | """Attempting to upload credentials to the server which already exist. - |
---|
221 | See MyProxyClient.store |
---|
222 | """ |
---|
223 | |
---|
224 | |
---|
225 | class MyProxyClientGetTrustRootsError(MyProxyClientError): |
---|
226 | """Error retrieving trust roots""" |
---|
227 | |
---|
228 | |
---|
229 | class MyProxyClient(object): |
---|
230 | """MyProxy client interface |
---|
231 | |
---|
232 | Based on protocol definitions in: |
---|
233 | |
---|
234 | http://grid.ncsa.uiuc.edu/myproxy/protocol/ |
---|
235 | |
---|
236 | @type MYPROXY_SERVER_ENVVARNAME: string |
---|
237 | @cvar MYPROXY_SERVER_ENVVARNAME: server environment variable name |
---|
238 | |
---|
239 | @type MYPROXY_SERVER_PORT_ENVVARNAME: string |
---|
240 | @cvar MYPROXY_SERVER_PORT_ENVVARNAME: port environment variable name |
---|
241 | |
---|
242 | @type MYPROXY_SERVER_DN_ENVVARNAME: string |
---|
243 | @cvar MYPROXY_SERVER_DN_ENVVARNAME: server certificate Distinguished Name |
---|
244 | environment variable name |
---|
245 | |
---|
246 | @type GLOBUS_LOCATION_ENVVARNAME: string |
---|
247 | @param GLOBUS_LOCATION_ENVVARNAME: 'GLOBUS_LOCATION' environment variable |
---|
248 | name |
---|
249 | |
---|
250 | @type GET_CMD: string |
---|
251 | @cvar GET_CMD: get command string |
---|
252 | |
---|
253 | @type INFO_CMD: string |
---|
254 | @cvar INFO_CMD: info command string |
---|
255 | |
---|
256 | @type DESTROY_CMD: string |
---|
257 | @cvar DESTROY_CMD: destroy command string |
---|
258 | |
---|
259 | @type CHANGE_PASSPHRASE_CMD: string |
---|
260 | @cvar CHANGE_PASSPHRASE_CMD: command string to change cred pass-phrase |
---|
261 | |
---|
262 | @type STORE_CMD: string |
---|
263 | @cvar STORE_CMD: store command string |
---|
264 | |
---|
265 | @type GET_TRUST_ROOTS_CMD: string |
---|
266 | @cvar GET_TRUST_ROOTS_CMD: get trust roots command string |
---|
267 | |
---|
268 | @type TRUSTED_CERTS_FIELDNAME: string |
---|
269 | @param TRUSTED_CERTS_FIELDNAME: field name in get trust roots response for |
---|
270 | trusted certificate file names |
---|
271 | |
---|
272 | @type TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX: string |
---|
273 | @param TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX: field name prefix in get |
---|
274 | trust roots response for trusted certificate file contents |
---|
275 | |
---|
276 | @type HOSTCERT_SUBDIRPATH: string |
---|
277 | @cvar HOSTCERT_SUBDIRPATH: sub-directory path host certificate (as tuple) |
---|
278 | |
---|
279 | @type HOSTKEY_SUBDIRPATH: string |
---|
280 | @cvar HOSTKEY_SUBDIRPATH: sub-directory path to host key (as tuple) |
---|
281 | |
---|
282 | @type PRIKEY_NBITS: int |
---|
283 | @cvar PRIKEY_NBITS: default number of bits for private key generated |
---|
284 | |
---|
285 | @type MESSAGE_DIGEST_TYPE: string |
---|
286 | @cvar MESSAGE_DIGEST_TYPE: message digest type is MD5 |
---|
287 | |
---|
288 | @type SERVER_RESP_BLK_SIZE: int |
---|
289 | @cvar SERVER_RESP_BLK_SIZE: block size for retrievals from server |
---|
290 | |
---|
291 | @type MAX_RECV_TRIES: int |
---|
292 | @cvar MAX_RECV_TRIES: maximum number of retrievals of size |
---|
293 | SERVER_RESP_BLK_SIZE before this client gives up |
---|
294 | |
---|
295 | @type DEF_PROXY_FILEPATH: string |
---|
296 | @cvar DEF_PROXY_FILEPATH: default location for proxy file to be written to |
---|
297 | |
---|
298 | @type PROXY_FILE_PERMISSIONS: int |
---|
299 | @cvar PROXY_FILE_PERMISSIONS: file permissions returned proxy file is |
---|
300 | created with |
---|
301 | |
---|
302 | @type PROPERTY_DEFAULTS: tuple |
---|
303 | @cvar PROPERTY_DEFAULTS: sets permissable element names for MyProxy config |
---|
304 | file |
---|
305 | |
---|
306 | @type ROOT_USERNAME: string |
---|
307 | @cvar ROOT_USERNAME: root username - used to determine output directory |
---|
308 | for trust roots |
---|
309 | |
---|
310 | @type ROOT_TRUSTROOT_DIR: string |
---|
311 | @param ROOT_TRUSTROOT_DIR: default trust root directory if running as root |
---|
312 | user |
---|
313 | |
---|
314 | @type USER_TRUSTROOT_DIR: string |
---|
315 | @param USER_TRUSTROOT_DIR: default trust root directory for users other |
---|
316 | than root |
---|
317 | |
---|
318 | @type X509_CERT_DIR_ENVVARNAME: string |
---|
319 | @param X509_CERT_DIR_ENVVARNAME: environment variable name 'X509_CERT_DIR', |
---|
320 | which if set points to the location of the trust roots |
---|
321 | """ |
---|
322 | MYPROXY_SERVER_ENVVARNAME = 'MYPROXY_SERVER' |
---|
323 | MYPROXY_SERVER_PORT_ENVVARNAME = 'MYPROXY_SERVER_PORT' |
---|
324 | MYPROXY_SERVER_DN_ENVVARNAME = 'MYPROXY_SERVER_DN' |
---|
325 | |
---|
326 | GLOBUS_LOCATION_ENVVARNAME = 'GLOBUS_LOCATION' |
---|
327 | |
---|
328 | GET_CMD="""VERSION=MYPROXYv2 |
---|
329 | COMMAND=0 |
---|
330 | USERNAME=%s |
---|
331 | PASSPHRASE=%s |
---|
332 | LIFETIME=%d""" |
---|
333 | |
---|
334 | PUT_CMD="""VERSION=MYPROXYv2 |
---|
335 | COMMAND=1 |
---|
336 | USERNAME=%s |
---|
337 | PASSPHRASE=<pass phrase> |
---|
338 | LIFETIME=%d""" |
---|
339 | |
---|
340 | INFO_CMD="""VERSION=MYPROXYv2 |
---|
341 | COMMAND=2 |
---|
342 | USERNAME=%s |
---|
343 | PASSPHRASE=PASSPHRASE |
---|
344 | LIFETIME=0""" |
---|
345 | |
---|
346 | DESTROY_CMD="""VERSION=MYPROXYv2 |
---|
347 | COMMAND=3 |
---|
348 | USERNAME=%s |
---|
349 | PASSPHRASE=PASSPHRASE |
---|
350 | LIFETIME=0""" |
---|
351 | |
---|
352 | CHANGE_PASSPHRASE_CMD="""VERSION=MYPROXYv2 |
---|
353 | COMMAND=4 |
---|
354 | USERNAME=%s |
---|
355 | PASSPHRASE=%s |
---|
356 | NEW_PHRASE=%s |
---|
357 | LIFETIME=0""" |
---|
358 | |
---|
359 | STORE_CMD="""VERSION=MYPROXYv2 |
---|
360 | COMMAND=5 |
---|
361 | USERNAME=%s |
---|
362 | PASSPHRASE= |
---|
363 | LIFETIME=%d""" |
---|
364 | |
---|
365 | GET_TRUST_ROOTS_CMD="""VERSION=MYPROXYv2 |
---|
366 | COMMAND=7 |
---|
367 | USERNAME=%s |
---|
368 | PASSPHRASE=%s |
---|
369 | LIFETIME=0 |
---|
370 | TRUSTED_CERTS=1""" |
---|
371 | |
---|
372 | TRUSTED_CERTS_FIELDNAME = 'TRUSTED_CERTS' |
---|
373 | TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX = 'FILEDATA_' |
---|
374 | |
---|
375 | HOSTCERT_SUBDIRPATH = ('etc', 'hostcert.pem') |
---|
376 | HOSTKEY_SUBDIRPATH = ('etc', 'hostkey.pem') |
---|
377 | |
---|
378 | PROXY_FILE_PERMISSIONS = 0600 |
---|
379 | |
---|
380 | # Work out default location of proxy file if it exists. This is set if a |
---|
381 | # call has been made previously to logon / get-delegation |
---|
382 | DEF_PROXY_FILEPATH = sys.platform == ('win32' and 'proxy' or |
---|
383 | sys.platform in ('linux2', 'darwin') and |
---|
384 | '/tmp/x509up_u%s' % (os.getuid()) |
---|
385 | or None) |
---|
386 | |
---|
387 | PRIKEY_NBITS = 4096 |
---|
388 | MESSAGE_DIGEST_TYPE = "md5" |
---|
389 | SERVER_RESP_BLK_SIZE = 8192 |
---|
390 | MAX_RECV_TRIES = 1024 |
---|
391 | |
---|
392 | # valid configuration property keywords |
---|
393 | PROPERTY_DEFAULTS = { |
---|
394 | 'hostname': 'localhost', |
---|
395 | 'port': 7512, |
---|
396 | 'cnPrefix': MyProxyServerSSLCertVerification.SERVER_CN_PREFIX, |
---|
397 | 'serverDN': None, |
---|
398 | 'openSSLConfFilePath': '', |
---|
399 | 'proxyCertMaxLifetime': 43200, |
---|
400 | 'proxyCertLifetime': 43200, |
---|
401 | 'caCertDir': None |
---|
402 | } |
---|
403 | |
---|
404 | ROOT_USERNAME = 'root' |
---|
405 | ROOT_TRUSTROOT_DIR = '/etc/grid-security/certificates' |
---|
406 | USER_TRUSTROOT_DIR = '~/.globus/certificates' |
---|
407 | X509_CERT_DIR_ENVVARNAME = 'X509_CERT_DIR' |
---|
408 | |
---|
409 | # Restrict attributes to the above properties, their equivalent |
---|
410 | # protected values + extra OpenSSL config object. |
---|
411 | __slots__ = tuple(['__' + k for k in PROPERTY_DEFAULTS.keys()]) |
---|
412 | del k |
---|
413 | __slots__ += ('__openSSLConfig', '__cfg', '__serverSSLCertVerify') |
---|
414 | |
---|
415 | def __init__(self, cfgFilePath=None, **prop): |
---|
416 | """Make any initial settings for client connections to MyProxy |
---|
417 | |
---|
418 | Settings are held in a dictionary which can be set from **prop, |
---|
419 | a call to setProperties() or by passing settings in an XML file |
---|
420 | given by cfgFilePath |
---|
421 | |
---|
422 | @param cfgFilePath: set properties via a configuration file |
---|
423 | @type cfgFilePath: basestring |
---|
424 | @param **prop: set properties via keywords - see |
---|
425 | PROPERTY_DEFAULTS class variable for a list of these |
---|
426 | @type **prop: dict |
---|
427 | """ |
---|
428 | self.__serverSSLCertVerify = MyProxyServerSSLCertVerification() |
---|
429 | self.__hostname = None |
---|
430 | self.__port = None |
---|
431 | self.__serverDN = None |
---|
432 | self.__openSSLConfFilePath = None |
---|
433 | self.__proxyCertMaxLifetime = MyProxyClient.PROPERTY_DEFAULTS[ |
---|
434 | 'proxyCertMaxLifetime'] |
---|
435 | self.__proxyCertLifetime = MyProxyClient.PROPERTY_DEFAULTS[ |
---|
436 | 'proxyCertLifetime'] |
---|
437 | self.__caCertDir = None |
---|
438 | |
---|
439 | self.__cfg = None |
---|
440 | |
---|
441 | # Configuration file used to get default subject when generating a |
---|
442 | # new proxy certificate request |
---|
443 | self.__openSSLConfig = OpenSSLConfig() |
---|
444 | |
---|
445 | # Server host name - take from environment variable if available |
---|
446 | self.hostname = os.environ.get(MyProxyClient.MYPROXY_SERVER_ENVVARNAME, |
---|
447 | MyProxyClient.PROPERTY_DEFAULTS['hostname']) |
---|
448 | |
---|
449 | # ... and port number |
---|
450 | self.port = int(os.environ.get( |
---|
451 | MyProxyClient.MYPROXY_SERVER_PORT_ENVVARNAME, |
---|
452 | MyProxyClient.PROPERTY_DEFAULTS['port'])) |
---|
453 | |
---|
454 | # Server Distinguished Name |
---|
455 | serverDN = os.environ.get(MyProxyClient.MYPROXY_SERVER_DN_ENVVARNAME, |
---|
456 | MyProxyClient.PROPERTY_DEFAULTS['serverDN']) |
---|
457 | if serverDN is not None: |
---|
458 | self.serverDN = serverDN |
---|
459 | |
---|
460 | # Set trust root - the directory containing the CA certificates for |
---|
461 | # verifying the MyProxy server's SSL certificate |
---|
462 | self.setDefaultCACertDir() |
---|
463 | |
---|
464 | # Any keyword settings override the defaults above |
---|
465 | for opt, val in prop.items(): |
---|
466 | setattr(self, opt, val) |
---|
467 | |
---|
468 | # If properties file is set any parameters settings in file will |
---|
469 | # override those set by input keyword or the defaults |
---|
470 | if cfgFilePath is not None: |
---|
471 | self.parseConfig(cfg=cfgFilePath) |
---|
472 | |
---|
473 | def setDefaultCACertDir(self): |
---|
474 | '''Make default trust root setting - the directory containing the CA |
---|
475 | certificates for verifying the MyProxy server's SSL certificate. |
---|
476 | |
---|
477 | The setting is made by using standard Globus defined locations and |
---|
478 | environment variable settings |
---|
479 | ''' |
---|
480 | |
---|
481 | # Check for X509_CERT_DIR environment variable |
---|
482 | x509CertDir = os.environ.get(MyProxyClient.X509_CERT_DIR_ENVVARNAME) |
---|
483 | if x509CertDir is not None: |
---|
484 | self.caCertDir = x509CertDir |
---|
485 | |
---|
486 | # Check for running as root user |
---|
487 | elif os.environ.get(MyProxyClient.ROOT_USERNAME) is not None: |
---|
488 | self.caCertDir = MyProxyClient.ROOT_TRUSTROOT_DIR |
---|
489 | |
---|
490 | # Default to non-root standard location |
---|
491 | else: |
---|
492 | self.caCertDir = os.path.expanduser( |
---|
493 | MyProxyClient.USER_TRUSTROOT_DIR) |
---|
494 | |
---|
495 | def _getServerSSLCertVerify(self): |
---|
496 | return self.__serverSSLCertVerify |
---|
497 | |
---|
498 | def _setServerSSLCertVerify(self, value): |
---|
499 | if not isinstance(value, MyProxyServerSSLCertVerification): |
---|
500 | raise TypeError('Expecting %r derived type for ' |
---|
501 | '"serverSSLCertVerify" attribute; got %r' % |
---|
502 | MyProxyServerSSLCertVerification, |
---|
503 | value) |
---|
504 | self.__serverSSLCertVerify = value |
---|
505 | |
---|
506 | serverSSLCertVerify = property(_getServerSSLCertVerify, |
---|
507 | _setServerSSLCertVerify, |
---|
508 | doc="Class with a __call__ method which is " |
---|
509 | "passed to the SSL context to verify " |
---|
510 | "the peer (MyProxy server) certificate " |
---|
511 | "in the SSL handshake between this " |
---|
512 | "client and the MyProxy server") |
---|
513 | |
---|
514 | def parseConfig(self, cfg, section='DEFAULT'): |
---|
515 | '''Extract parameters from _cfg config object''' |
---|
516 | |
---|
517 | if isinstance(cfg, basestring): |
---|
518 | cfgFilePath = os.path.expandvars(cfg) |
---|
519 | self.__cfg = CaseSensitiveConfigParser() |
---|
520 | self.__cfg.read(cfgFilePath) |
---|
521 | else: |
---|
522 | cfgFilePath = None |
---|
523 | self.__cfg = cfg |
---|
524 | |
---|
525 | for key, val in self.__cfg.items(section): |
---|
526 | setattr(self, key, val) |
---|
527 | |
---|
528 | # Get/Set Property methods |
---|
529 | def _getHostname(self): |
---|
530 | return self.__hostname |
---|
531 | |
---|
532 | def _setHostname(self, val): |
---|
533 | """Also sets SSL Certificate verification object property!""" |
---|
534 | if not isinstance(val, basestring): |
---|
535 | raise TypeError("Expecting string type for hostname " |
---|
536 | "attribute") |
---|
537 | self.__hostname = val |
---|
538 | self.__serverSSLCertVerify.hostname = val |
---|
539 | |
---|
540 | hostname = property(fget=_getHostname, |
---|
541 | fset=_setHostname, |
---|
542 | doc="hostname of MyProxy server") |
---|
543 | |
---|
544 | def _getPort(self): |
---|
545 | return self.__port |
---|
546 | |
---|
547 | def _setPort(self, val): |
---|
548 | if isinstance(val, basestring): |
---|
549 | self.__port = int(val) |
---|
550 | elif isinstance(val, int): |
---|
551 | self.__port = val |
---|
552 | else: |
---|
553 | raise TypeError("Expecting int type for port attribute") |
---|
554 | |
---|
555 | port = property(fget=_getPort, |
---|
556 | fset=_setPort, |
---|
557 | doc="Port number for MyProxy server") |
---|
558 | |
---|
559 | def _getServerDN(self): |
---|
560 | return self.__serverDN |
---|
561 | |
---|
562 | def _setServerDN(self, val): |
---|
563 | """Also sets SSL Certificate verification object property!""" |
---|
564 | if not isinstance(val, basestring): |
---|
565 | raise TypeError("Expecting string type for serverDN attribute") |
---|
566 | |
---|
567 | self.__serverDN = val |
---|
568 | self.__serverSSLCertVerify.certDN = val |
---|
569 | |
---|
570 | serverDN = property(fget=_getServerDN, |
---|
571 | fset=_setServerDN, |
---|
572 | doc="Distinguished Name for MyProxy Server " |
---|
573 | "Certificate") |
---|
574 | |
---|
575 | def _getServerCNPrefix(self): |
---|
576 | """References SSL Certificate verification object property!""" |
---|
577 | return self.__serverSSLCertVerify.cnPrefix |
---|
578 | |
---|
579 | def _setServerCNPrefix(self, val): |
---|
580 | """Sets SSL Certificate verification object property!""" |
---|
581 | self.__serverSSLCertVerify.cnPrefix = val |
---|
582 | |
---|
583 | serverCNPrefix = property(fget=_getServerCNPrefix, |
---|
584 | fset=_setServerCNPrefix, |
---|
585 | doc="Prefix for MyProxy Server Certificate " |
---|
586 | "Distinguished Name CoomonName field; " |
---|
587 | "usually set to host/ for Globus host " |
---|
588 | "certificates") |
---|
589 | |
---|
590 | def _getOpenSSLConfFilePath(self): |
---|
591 | return self.__openSSLConfFilePath |
---|
592 | |
---|
593 | def _setOpenSSLConfFilePath(self, val): |
---|
594 | if not isinstance(val, basestring): |
---|
595 | raise TypeError('Expecting string type for "openSSLConfFilePath" ' |
---|
596 | 'attribute') |
---|
597 | |
---|
598 | self.__openSSLConfFilePath = os.path.expandvars(val) |
---|
599 | self.__openSSLConfig.filePath = self.__openSSLConfFilePath |
---|
600 | self.__openSSLConfig.read() |
---|
601 | |
---|
602 | openSSLConfFilePath = property(fget=_getOpenSSLConfFilePath, |
---|
603 | fset=_setOpenSSLConfFilePath, |
---|
604 | doc="file path for OpenSSL config file") |
---|
605 | |
---|
606 | def _getProxyCertMaxLifetime(self): |
---|
607 | return self.__proxyCertMaxLifetime |
---|
608 | |
---|
609 | def _setProxyCertMaxLifetime(self, val): |
---|
610 | if isinstance(val, basestring): |
---|
611 | self.__proxyCertMaxLifetime = int(val) |
---|
612 | |
---|
613 | elif isinstance(val, int): |
---|
614 | self.__proxyCertMaxLifetime = val |
---|
615 | else: |
---|
616 | raise TypeError("Expecting int type for proxyCertMaxLifetime " |
---|
617 | "attribute") |
---|
618 | |
---|
619 | proxyCertMaxLifetime = property(fget=_getProxyCertMaxLifetime, |
---|
620 | fset=_setProxyCertMaxLifetime, |
---|
621 | doc="Default max. lifetime allowed for " |
---|
622 | "Proxy Certificate retrieved - used " |
---|
623 | "by store method") |
---|
624 | |
---|
625 | def _getProxyCertLifetime(self): |
---|
626 | return self.__proxyCertLifetime |
---|
627 | |
---|
628 | def _setProxyCertLifetime(self, val): |
---|
629 | if isinstance(val, basestring): |
---|
630 | self.__proxyCertLifetime = int(val) |
---|
631 | elif isinstance(val, int): |
---|
632 | self.__proxyCertLifetime = val |
---|
633 | else: |
---|
634 | raise TypeError("Expecting int type for proxyCertLifetime " |
---|
635 | "attribute") |
---|
636 | |
---|
637 | proxyCertLifetime = property(fget=_getProxyCertLifetime, |
---|
638 | fset=_setProxyCertLifetime, |
---|
639 | doc="Default proxy cert. lifetime used in " |
---|
640 | "logon request") |
---|
641 | |
---|
642 | def _getCACertDir(self): |
---|
643 | return self.__caCertDir |
---|
644 | |
---|
645 | def _setCACertDir(self, val): |
---|
646 | '''Specify a directory containing PEM encoded CA certs. used for |
---|
647 | validation of MyProxy server certificate. |
---|
648 | |
---|
649 | Set to None to make OpenSSL.SSL.Context.load_verify_locations ignore |
---|
650 | this parameter |
---|
651 | |
---|
652 | @type val: basestring/None |
---|
653 | @param val: directory path''' |
---|
654 | |
---|
655 | if isinstance(val, basestring): |
---|
656 | if val == '': |
---|
657 | self.__caCertDir = None |
---|
658 | else: |
---|
659 | self.__caCertDir = os.path.expandvars(val) |
---|
660 | |
---|
661 | elif isinstance(val, None): |
---|
662 | self.__caCertDir = val |
---|
663 | else: |
---|
664 | raise TypeError("Expecting string or None type for caCertDir " |
---|
665 | "attribute") |
---|
666 | |
---|
667 | caCertDir = property(fget=_getCACertDir, |
---|
668 | fset=_setCACertDir, |
---|
669 | doc="trust roots directory containing PEM encoded CA " |
---|
670 | "certificates to validate MyProxy server " |
---|
671 | "certificate") |
---|
672 | |
---|
673 | |
---|
674 | def _getOpenSSLConfig(self): |
---|
675 | "Get OpenSSLConfig object property method" |
---|
676 | return self.__openSSLConfig |
---|
677 | |
---|
678 | openSSLConfig = property(fget=_getOpenSSLConfig, doc="OpenSSLConfig object") |
---|
679 | |
---|
680 | def _initConnection(self, |
---|
681 | certFile=None, |
---|
682 | keyFile=None, |
---|
683 | keyFilePassphrase=None, |
---|
684 | verifyPeerWithTrustRoots=True): |
---|
685 | """Initialise connection setting up SSL context and client and |
---|
686 | server side identity checks |
---|
687 | |
---|
688 | @type sslCertFile: basestring |
---|
689 | @param sslCertFile: certificate for SSL client authentication. It may |
---|
690 | be owner of a credential to be acted on or the concatenated proxy |
---|
691 | certificate + proxy's signing cert. SSL client authentication is not |
---|
692 | necessary for getDelegation / logon calls |
---|
693 | @type sslKeyFile: basestring |
---|
694 | @param sslKeyFile: client private key file |
---|
695 | @type keyFilePassphrase: basestring |
---|
696 | @param keyFilePassphrase: pass-phrase protecting private key if set |
---|
697 | @type verifyPeerWithTrustRoots: bool |
---|
698 | @param verifyPeerWithTrustRoots: verify MyProxy server's SSL certificate |
---|
699 | against a list of trusted CA certificates in the CA certificate |
---|
700 | directory set by the "CaCertDir" attribute. This should always be set |
---|
701 | to True for MyProxy client calls unless using the 'bootstrap' trust |
---|
702 | roots mode available with logon and get trust roots calls |
---|
703 | """ |
---|
704 | # Must be version 3 for MyProxy |
---|
705 | context = SSL.Context(SSL.SSLv3_METHOD) |
---|
706 | |
---|
707 | if verifyPeerWithTrustRoots: |
---|
708 | context.load_verify_locations(None, self.caCertDir) |
---|
709 | |
---|
710 | # Verify peer's (MyProxy server) certificate |
---|
711 | context.set_verify(SSL.VERIFY_PEER, self.__serverSSLCertVerify) |
---|
712 | |
---|
713 | if certFile: |
---|
714 | try: |
---|
715 | context.use_certificate_chain_file(certFile) |
---|
716 | def pwdCallback(passphraseMaxLen, |
---|
717 | promptPassphraseTwice, |
---|
718 | passphrase): |
---|
719 | """Private key file password callback function""" |
---|
720 | if len(passphrase) > passphraseMaxLen: |
---|
721 | log.error('Passphrase length %d is greater than the ' |
---|
722 | 'maximum length allowed %d', |
---|
723 | len(passphrase), passphraseMaxLen) |
---|
724 | return '' |
---|
725 | |
---|
726 | return passphrase |
---|
727 | |
---|
728 | if keyFilePassphrase is not None: |
---|
729 | context.set_passwd_cb(pwdCallback, keyFilePassphrase) |
---|
730 | |
---|
731 | context.use_privatekey_file(keyFile) |
---|
732 | except Exception: |
---|
733 | raise MyProxyClientConfigError("Loading certificate " |
---|
734 | "and private key for SSL " |
---|
735 | "connection [also check CA " |
---|
736 | "certificate settings]: %s" % |
---|
737 | traceback.format_exc()) |
---|
738 | |
---|
739 | # Disable for compatibility with myproxy server (er, globus) |
---|
740 | # globus doesn't handle this case, apparently, and instead |
---|
741 | # chokes in proxy delegation code |
---|
742 | context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS) |
---|
743 | |
---|
744 | # connect to myproxy server |
---|
745 | conn = SSL.Connection(context, socket.socket()) |
---|
746 | |
---|
747 | return conn |
---|
748 | |
---|
749 | def _createKeyPair(self, nBitsForKey=PRIKEY_NBITS): |
---|
750 | """Generate key pair and return as PEM encoded string |
---|
751 | @type nBitsForKey: int |
---|
752 | @param nBitsForKey: number of bits for private key generation - |
---|
753 | default is 2048 |
---|
754 | @rtype: OpenSSL.crypto.PKey |
---|
755 | @return: public/private key pair |
---|
756 | """ |
---|
757 | keyPair = crypto.PKey() |
---|
758 | keyPair.generate_key(crypto.TYPE_RSA, nBitsForKey) |
---|
759 | |
---|
760 | return keyPair |
---|
761 | |
---|
762 | def _createCertReq(self, CN, keyPair, messageDigest=MESSAGE_DIGEST_TYPE): |
---|
763 | """Create a certificate request. |
---|
764 | |
---|
765 | @type CN: basestring |
---|
766 | @param CN: Common Name for certificate - effectively the same as the |
---|
767 | username for the MyProxy credential |
---|
768 | @type keyPair: string/None |
---|
769 | @param keyPair: public/private key pair |
---|
770 | @type messageDigest: basestring |
---|
771 | @param messageDigest: message digest type - default is MD5 |
---|
772 | @rtype: base string |
---|
773 | @return certificate request PEM text and private key PEM text |
---|
774 | """ |
---|
775 | |
---|
776 | # Check all required certifcate request DN parameters are set |
---|
777 | # Create certificate request |
---|
778 | certReq = crypto.X509Req() |
---|
779 | |
---|
780 | # Create public key object |
---|
781 | certReq.set_pubkey(keyPair) |
---|
782 | |
---|
783 | # Add the public key to the request |
---|
784 | certReq.sign(keyPair, messageDigest) |
---|
785 | |
---|
786 | derCertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, |
---|
787 | certReq) |
---|
788 | |
---|
789 | return derCertReq |
---|
790 | |
---|
791 | def _deserializeResponse(self, msg, *fieldNames): |
---|
792 | """ |
---|
793 | Deserialize a MyProxy server response |
---|
794 | |
---|
795 | @param msg: string response message from MyProxy server |
---|
796 | @return: tuple of integer response and errorTxt string (if any) and all |
---|
797 | the fields parsed. fields is a list of two element, field name, field |
---|
798 | value tuples. |
---|
799 | @rtype: tuple |
---|
800 | """ |
---|
801 | lines = msg.split('\n') |
---|
802 | fields = [tuple(line.split('=', 1)) for line in lines][:-1] |
---|
803 | |
---|
804 | # get response value |
---|
805 | respCode = [int(v) for k, v in fields if k == 'RESPONSE'][0] |
---|
806 | |
---|
807 | # get error text |
---|
808 | errorTxt = os.linesep.join([v for k, v in fields if k == 'ERROR']) |
---|
809 | |
---|
810 | # Check for custom fields requested by caller to this method |
---|
811 | if fieldNames: |
---|
812 | fieldsDict = {} |
---|
813 | for k, v in fields: |
---|
814 | names = [name for name in fieldNames if k.startswith(name)] |
---|
815 | if len(names) == 0: |
---|
816 | continue |
---|
817 | else: |
---|
818 | if v.isdigit(): |
---|
819 | fieldsDict[k] = int(v) |
---|
820 | else: |
---|
821 | fieldsDict[k] = v |
---|
822 | |
---|
823 | # Return additional dict item in tuple |
---|
824 | return respCode, errorTxt, fieldsDict |
---|
825 | else: |
---|
826 | return respCode, errorTxt |
---|
827 | |
---|
828 | def _deserializeCerts(self, inputDat): |
---|
829 | """Unpack certificates returned from a get delegation call to the |
---|
830 | server |
---|
831 | |
---|
832 | @param inputDat: string containing the proxy cert and private key |
---|
833 | and signing cert all in DER format |
---|
834 | |
---|
835 | @return list containing the equivalent to the input in PEM format""" |
---|
836 | pemCerts = [] |
---|
837 | dat = inputDat |
---|
838 | |
---|
839 | while dat: |
---|
840 | # find start of cert, get length |
---|
841 | ind = dat.find('\x30\x82') |
---|
842 | if ind < 0: |
---|
843 | break |
---|
844 | |
---|
845 | len = 256*ord(dat[ind+2]) + ord(dat[ind+3]) |
---|
846 | |
---|
847 | # extract der-format cert, and convert to pem |
---|
848 | derCert = dat[ind:ind+len+4] |
---|
849 | x509Cert = crypto.load_certificate(crypto.FILETYPE_ASN1, derCert) |
---|
850 | pemCert = crypto.dump_certificate(crypto.FILETYPE_PEM, x509Cert) |
---|
851 | pemCerts.append(pemCert) |
---|
852 | |
---|
853 | # trim cert from data |
---|
854 | dat = dat[ind + len + 4:] |
---|
855 | |
---|
856 | return pemCerts |
---|
857 | |
---|
858 | @classmethod |
---|
859 | def writeProxyFile(cls, proxyCert, proxyPriKey, userX509Cert, |
---|
860 | filePath=None): |
---|
861 | """Write out proxy cert to file in the same way as myproxy-logon - |
---|
862 | proxy cert, private key, user cert. Nb. output from logon can be |
---|
863 | passed direct into this method |
---|
864 | |
---|
865 | @type proxyCert: string |
---|
866 | @param proxyCert: proxy certificate |
---|
867 | @type proxyPriKey: string |
---|
868 | @param proxyPriKey: private key for proxy |
---|
869 | @type userX509Cert: string |
---|
870 | @param userX509Cert: user certificate which issued the proxy |
---|
871 | @type filePath: string |
---|
872 | @param filePath: set to override the default filePath""" |
---|
873 | |
---|
874 | if filePath is None: |
---|
875 | filePath = MyProxyClient.DEF_PROXY_FILEPATH |
---|
876 | |
---|
877 | if filePath is None: |
---|
878 | MyProxyClientConfigError("Error setting proxy file path - invalid " |
---|
879 | "platform?") |
---|
880 | |
---|
881 | outStr = proxyCert + proxyPriKey + userX509Cert |
---|
882 | open(MyProxyClient.DEF_PROXY_FILEPATH, 'w').write(outStr) |
---|
883 | try: |
---|
884 | # Make sure permissions are set correctly |
---|
885 | os.chmod(MyProxyClient.DEF_PROXY_FILEPATH, |
---|
886 | MyProxyClient.PROXY_FILE_PERMISSIONS) |
---|
887 | except Exception, e: |
---|
888 | # Don't leave the file lying around if couldn't change it's |
---|
889 | # permissions |
---|
890 | os.unlink(MyProxyClient.DEF_PROXY_FILEPATH) |
---|
891 | |
---|
892 | log.error('Unable to set %o permissions for proxy file "%s": %s'% |
---|
893 | (MyProxyClient.PROXY_FILE_PERMISSIONS, |
---|
894 | MyProxyClient.DEF_PROXY_FILEPATH, e)) |
---|
895 | raise |
---|
896 | |
---|
897 | @classmethod |
---|
898 | def readProxyFile(cls, filePath=None): |
---|
899 | """Read proxy cert file following the format used by myproxy-logon - |
---|
900 | proxy, cert, private key, user cert. |
---|
901 | |
---|
902 | @rtype: tuple |
---|
903 | @return: tuple containing proxy cert, private key, user cert""" |
---|
904 | if filePath is None: |
---|
905 | filePath = MyProxyClient.DEF_PROXY_FILEPATH |
---|
906 | |
---|
907 | if filePath is None: |
---|
908 | MyProxyClientConfigError("Error setting proxy file path - invalid " |
---|
909 | "platform?") |
---|
910 | |
---|
911 | proxy = open(MyProxyClient.DEF_PROXY_FILEPATH).read() |
---|
912 | |
---|
913 | # Split certs and key into separate tuple items |
---|
914 | return tuple(['-----BEGIN'+i for i in proxy.split('-----BEGIN')[1:]]) |
---|
915 | |
---|
916 | def put(self, |
---|
917 | username, |
---|
918 | passphrase, |
---|
919 | userCertFile, |
---|
920 | userKeyFile, |
---|
921 | lifetime=None, |
---|
922 | sslCertFile=None, |
---|
923 | sslKeyFile=None, |
---|
924 | sslKeyFilePassphrase=None): |
---|
925 | """Store a proxy credential on the server |
---|
926 | |
---|
927 | Unfortunately this method is not implemented as it requires the creation |
---|
928 | of a proxy certificate by the client but PyOpenSSL doesn't currently |
---|
929 | support the required proxyCertInfo X.509 certificate extension |
---|
930 | |
---|
931 | @raise NotImplementedError: see above |
---|
932 | |
---|
933 | @type username: string |
---|
934 | @param username: username selected for new credential |
---|
935 | @type passphrase: string |
---|
936 | @param passphrase: pass-phrase for new credential. This will be used |
---|
937 | by the server to authenticate later requests. IT must be at least |
---|
938 | 6 characters. The server may impose other restrictions too depending |
---|
939 | on its configuration. |
---|
940 | @type certFile: string |
---|
941 | @param certFile: user's X.509 proxy certificate in PEM format |
---|
942 | @type keyFile: string |
---|
943 | @param keyFile: equivalent private key file in PEM format |
---|
944 | @type sslCertFile: string |
---|
945 | @param sslCertFile: certificate used for client authentication with |
---|
946 | the MyProxy server SSL connection. If not set, |
---|
947 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
948 | @type sslKeyFile: string |
---|
949 | @param sslKeyFile: corresponding private key file. See explanation |
---|
950 | for sslCertFile |
---|
951 | @type sslKeyFilePassphrase: string |
---|
952 | @param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the |
---|
953 | private key is not password protected. |
---|
954 | @type lifetime: int / None |
---|
955 | @param lifetime: the maximum lifetime allowed for retrieved proxy |
---|
956 | credentials in seconds. defaults to proxyCertMaxLifetime attribute value |
---|
957 | """ |
---|
958 | raise NotImplementedError('put method is not currently implemented. ' |
---|
959 | 'It requires the creation of a proxy ' |
---|
960 | 'certificate by the client but PyOpenSSL ' |
---|
961 | 'doesn\'t currently support the required ' |
---|
962 | 'proxyCertInfo X.509 certificate extension.') |
---|
963 | |
---|
964 | def info(self, |
---|
965 | username, |
---|
966 | sslCertFile=None, |
---|
967 | sslKeyFile=None, |
---|
968 | sslKeyFilePassphrase=None): |
---|
969 | """return True/False whether credentials exist on the server for a |
---|
970 | given username |
---|
971 | |
---|
972 | @raise MyProxyClientGetError: |
---|
973 | @raise MyProxyClientRetrieveError: |
---|
974 | |
---|
975 | @type username: string |
---|
976 | @param username: username selected for credential |
---|
977 | @type sslCertFile: string |
---|
978 | @param sslCertFile: certificate used for client authentication with |
---|
979 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
980 | of the stored credentials. Only the owner can later remove |
---|
981 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
982 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
983 | @type sslKeyFile: string |
---|
984 | @param sslKeyFile: corresponding private key file. See explanation |
---|
985 | for sslCertFile |
---|
986 | @type sslKeyFilePassphrase: string |
---|
987 | @param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the |
---|
988 | private key is not password protected. |
---|
989 | """ |
---|
990 | globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME) |
---|
991 | if not sslCertFile: |
---|
992 | if globusLoc: |
---|
993 | sslCertFile = os.path.join(globusLoc, |
---|
994 | *MyProxyClient.HOSTCERT_SUBDIRPATH) |
---|
995 | sslKeyFile = os.path.join(globusLoc, |
---|
996 | *MyProxyClient.HOSTKEY_SUBDIRPATH) |
---|
997 | else: |
---|
998 | raise MyProxyClientError( |
---|
999 | "No client authentication cert. and private key file were given") |
---|
1000 | |
---|
1001 | # Set-up SSL connection |
---|
1002 | conn = self._initConnection(certFile=sslCertFile, |
---|
1003 | keyFile=sslKeyFile, |
---|
1004 | keyFilePassphrase=sslKeyFilePassphrase) |
---|
1005 | |
---|
1006 | conn.connect((self.hostname, self.port)) |
---|
1007 | |
---|
1008 | # send globus compatibility stuff |
---|
1009 | conn.write('0') |
---|
1010 | |
---|
1011 | # send info command - ensure conversion from unicode before writing |
---|
1012 | cmd = MyProxyClient.INFO_CMD % username |
---|
1013 | conn.write(str(cmd)) |
---|
1014 | |
---|
1015 | # process server response |
---|
1016 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1017 | |
---|
1018 | # Pass in the names of fields to return in the dictionary 'field' |
---|
1019 | respCode, errorTxt, field = self._deserializeResponse(dat, |
---|
1020 | 'CRED_START_TIME', |
---|
1021 | 'CRED_END_TIME', |
---|
1022 | 'CRED_OWNER') |
---|
1023 | |
---|
1024 | return not bool(respCode), errorTxt, field |
---|
1025 | |
---|
1026 | def changePassphrase(self, |
---|
1027 | username, |
---|
1028 | passphrase, |
---|
1029 | newPassphrase, |
---|
1030 | sslCertFile=None, |
---|
1031 | sslKeyFile=None, |
---|
1032 | sslKeyFilePassphrase=None): |
---|
1033 | """change pass-phrase protecting the credentials for a given username |
---|
1034 | |
---|
1035 | @raise MyProxyClientGetError: |
---|
1036 | @raise MyProxyClientRetrieveError: |
---|
1037 | |
---|
1038 | @param username: username of credential |
---|
1039 | @param passphrase: existing pass-phrase for credential |
---|
1040 | @param newPassphrase: new pass-phrase to replace the existing one. |
---|
1041 | @param sslCertFile: certificate used for client authentication with |
---|
1042 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
1043 | of the stored credentials. Only the owner can later remove |
---|
1044 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
1045 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
1046 | @param sslKeyFile: corresponding private key file. See explanation |
---|
1047 | for sslCertFile |
---|
1048 | @param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the |
---|
1049 | private key is not password protected. |
---|
1050 | @return none |
---|
1051 | """ |
---|
1052 | globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME) |
---|
1053 | if not sslCertFile or not sslKeyFile: |
---|
1054 | if globusLoc: |
---|
1055 | sslCertFile = os.path.join(globusLoc, |
---|
1056 | *MyProxyClient.HOSTCERT_SUBDIRPATH) |
---|
1057 | sslKeyFile = os.path.join(globusLoc, |
---|
1058 | *MyProxyClient.HOSTKEY_SUBDIRPATH) |
---|
1059 | else: |
---|
1060 | raise MyProxyClientError( |
---|
1061 | "No client authentication cert. and private key file were given") |
---|
1062 | |
---|
1063 | # Set-up SSL connection |
---|
1064 | conn = self._initConnection(certFile=sslCertFile, |
---|
1065 | keyFile=sslKeyFile, |
---|
1066 | keyFilePassphrase=sslKeyFilePassphrase) |
---|
1067 | |
---|
1068 | conn.connect((self.hostname, self.port)) |
---|
1069 | |
---|
1070 | # send globus compatibility stuff |
---|
1071 | conn.write('0') |
---|
1072 | |
---|
1073 | # send command - ensure conversion from unicode before writing |
---|
1074 | cmd = MyProxyClient.CHANGE_PASSPHRASE_CMD % (username, |
---|
1075 | passphrase, |
---|
1076 | newPassphrase) |
---|
1077 | conn.write(str(cmd)) |
---|
1078 | |
---|
1079 | # process server response |
---|
1080 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1081 | |
---|
1082 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
1083 | if respCode: |
---|
1084 | raise MyProxyClientGetError(errorTxt) |
---|
1085 | |
---|
1086 | def destroy(self, |
---|
1087 | username, |
---|
1088 | sslCertFile=None, |
---|
1089 | sslKeyFile=None, |
---|
1090 | sslKeyFilePassphrase=None): |
---|
1091 | """destroy credentials from the server for a given username |
---|
1092 | |
---|
1093 | @raise MyProxyClientGetError: |
---|
1094 | @raise MyProxyClientRetrieveError: |
---|
1095 | |
---|
1096 | @param username: username selected for credential |
---|
1097 | @param sslCertFile: certificate used for client authentication with |
---|
1098 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
1099 | of the stored credentials. Only the owner can later remove |
---|
1100 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
1101 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem |
---|
1102 | @param sslKeyFile: corresponding private key file. See explanation |
---|
1103 | for sslCertFile |
---|
1104 | @param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the |
---|
1105 | private key is not password protected. |
---|
1106 | @return none |
---|
1107 | """ |
---|
1108 | globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME) |
---|
1109 | if not sslCertFile or not sslKeyFile: |
---|
1110 | if globusLoc: |
---|
1111 | sslCertFile = os.path.join(globusLoc, |
---|
1112 | *MyProxyClient.HOSTCERT_SUBDIRPATH) |
---|
1113 | sslKeyFile = os.path.join(globusLoc, |
---|
1114 | *MyProxyClient.HOSTKEY_SUBDIRPATH) |
---|
1115 | else: |
---|
1116 | raise MyProxyClientError( |
---|
1117 | "No client authentication cert. and private key file were given") |
---|
1118 | |
---|
1119 | # Set-up SSL connection |
---|
1120 | conn = self._initConnection(certFile=sslCertFile, |
---|
1121 | keyFile=sslKeyFile, |
---|
1122 | keyFilePassphrase=sslKeyFilePassphrase) |
---|
1123 | |
---|
1124 | conn.connect((self.hostname, self.port)) |
---|
1125 | |
---|
1126 | # send globus compatibility stuff |
---|
1127 | conn.write('0') |
---|
1128 | |
---|
1129 | # send destroy command - ensure conversion from unicode before writing |
---|
1130 | cmd = MyProxyClient.DESTROY_CMD % username |
---|
1131 | conn.write(str(cmd)) |
---|
1132 | |
---|
1133 | # process server response |
---|
1134 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1135 | |
---|
1136 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
1137 | if respCode: |
---|
1138 | raise MyProxyClientGetError(errorTxt) |
---|
1139 | |
---|
1140 | def store(self, |
---|
1141 | username, |
---|
1142 | passphrase, |
---|
1143 | certFile, |
---|
1144 | keyFile, |
---|
1145 | sslCertFile=None, |
---|
1146 | sslKeyFile=None, |
---|
1147 | sslKeyFilePassphrase=None, |
---|
1148 | lifetime=None, |
---|
1149 | force=True): |
---|
1150 | """Upload credentials to the server |
---|
1151 | |
---|
1152 | @raise MyProxyClientGetError: |
---|
1153 | @raise MyProxyClientRetrieveError: |
---|
1154 | |
---|
1155 | @type username: string |
---|
1156 | @param username: username selected for new credential |
---|
1157 | @type passphrase: string |
---|
1158 | @param passphrase: pass-phrase for new credential. This is the pass |
---|
1159 | phrase which protects keyfile. |
---|
1160 | @type certFile: string |
---|
1161 | @param certFile: user's X.509 certificate in PEM format |
---|
1162 | @type keyFile: string |
---|
1163 | @param keyFile: equivalent private key file in PEM format |
---|
1164 | @type sslCertFile: string |
---|
1165 | @param sslCertFile: certificate used for client authentication with |
---|
1166 | the MyProxy server SSL connection. This ID will be set as the owner |
---|
1167 | of the stored credentials. Only the owner can later remove |
---|
1168 | credentials with myproxy-destroy or the destroy method. If not set, |
---|
1169 | this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this |
---|
1170 | is not set, certFile |
---|
1171 | @type sslKeyFile: string |
---|
1172 | @param sslKeyFile: corresponding private key file. See explanation |
---|
1173 | for sslCertFile |
---|
1174 | @type sslKeyFilePassphrase: string |
---|
1175 | @param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the |
---|
1176 | private key is not password protected. Nb. keyFile is expected to |
---|
1177 | be passphrase protected as this will be the passphrase used for |
---|
1178 | logon / getDelegation. |
---|
1179 | @type Force: bool |
---|
1180 | @param force: set to True to overwrite any existing creds with the |
---|
1181 | same username. If, force=False a check is made with a call to info. |
---|
1182 | If creds already, exist exit without proceeding |
---|
1183 | """ |
---|
1184 | |
---|
1185 | lifetime = lifetime or self.proxyCertMaxLifetime |
---|
1186 | |
---|
1187 | # Inputs must be string type otherwise server will reject the request |
---|
1188 | if isinstance(username, unicode): |
---|
1189 | username = str(username) |
---|
1190 | |
---|
1191 | if isinstance(passphrase, unicode): |
---|
1192 | passphrase = str(passphrase) |
---|
1193 | |
---|
1194 | globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME) |
---|
1195 | if not sslCertFile or not sslKeyFile: |
---|
1196 | if globusLoc: |
---|
1197 | sslCertFile = os.path.join(globusLoc, |
---|
1198 | *MyProxyClient.HOSTCERT_SUBDIRPATH) |
---|
1199 | sslKeyFile = os.path.join(globusLoc, |
---|
1200 | *MyProxyClient.HOSTKEY_SUBDIRPATH) |
---|
1201 | else: |
---|
1202 | # Default so that the owner is the same as the ID of the |
---|
1203 | # credentials to be uploaded. |
---|
1204 | sslCertFile = certFile |
---|
1205 | sslKeyFile = keyFile |
---|
1206 | sslKeyFilePassphrase = passphrase |
---|
1207 | |
---|
1208 | if not force: |
---|
1209 | # Check credentials don't already exist |
---|
1210 | if self.info(username, |
---|
1211 | sslCertFile=sslCertFile, |
---|
1212 | sslKeyFile=sslKeyFile, |
---|
1213 | sslKeyFilePassphrase=sslKeyFilePassphrase)[0]: |
---|
1214 | raise MyProxyCredentialsAlreadyExist( |
---|
1215 | "Credentials already exist for user: %s" % username) |
---|
1216 | |
---|
1217 | # Set up SSL connection |
---|
1218 | conn = self._initConnection(certFile=sslCertFile, |
---|
1219 | keyFile=sslKeyFile, |
---|
1220 | keyFilePassphrase=sslKeyFilePassphrase) |
---|
1221 | |
---|
1222 | conn.connect((self.hostname, self.port)) |
---|
1223 | |
---|
1224 | # send globus compatibility stuff |
---|
1225 | conn.write('0') |
---|
1226 | |
---|
1227 | # send store command - ensure conversion from unicode before writing |
---|
1228 | cmd = MyProxyClient.STORE_CMD % (username, lifetime) |
---|
1229 | conn.write(str(cmd)) |
---|
1230 | |
---|
1231 | # process server response |
---|
1232 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1233 | |
---|
1234 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
1235 | if respCode: |
---|
1236 | raise MyProxyClientGetError(errorTxt) |
---|
1237 | |
---|
1238 | # Send certificate and private key |
---|
1239 | certTxt = open(certFile).read() |
---|
1240 | keyTxt = open(keyFile).read() |
---|
1241 | |
---|
1242 | conn.send(certTxt + keyTxt) |
---|
1243 | |
---|
1244 | |
---|
1245 | # process server response |
---|
1246 | resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1247 | respCode, errorTxt = self._deserializeResponse(resp) |
---|
1248 | if respCode: |
---|
1249 | raise MyProxyClientRetrieveError(errorTxt) |
---|
1250 | |
---|
1251 | def logon(self, |
---|
1252 | username, |
---|
1253 | passphrase, |
---|
1254 | lifetime=None, |
---|
1255 | keyPair=None, |
---|
1256 | certReq=None, |
---|
1257 | nBitsForKey=PRIKEY_NBITS, |
---|
1258 | bootstrap=False, |
---|
1259 | updateTrustRoots=False, |
---|
1260 | authnGetTrustRootsCall=False): |
---|
1261 | """Retrieve a proxy credential from a MyProxy server |
---|
1262 | |
---|
1263 | Exceptions: MyProxyClientGetError, MyProxyClientRetrieveError |
---|
1264 | |
---|
1265 | @type username: basestring |
---|
1266 | @param username: username of credential |
---|
1267 | |
---|
1268 | @type passphrase: basestring |
---|
1269 | @param passphrase: pass-phrase for private key of credential held on |
---|
1270 | server |
---|
1271 | |
---|
1272 | @type lifetime: int |
---|
1273 | @param lifetime: lifetime for generated certificate |
---|
1274 | |
---|
1275 | @type keyPair: OpenSSL.crypto.PKey |
---|
1276 | @param keyPair: Public/Private key pair. This is ignored if a |
---|
1277 | certificate request is passed via the certReq keyword |
---|
1278 | |
---|
1279 | @type certReq: string |
---|
1280 | @param certReq: ASN1 format certificate request, if none set, one is |
---|
1281 | created along with a key pair |
---|
1282 | |
---|
1283 | @type nBitsForKey: int |
---|
1284 | @param nBitsForKey: number of bits to use when generating key pair, |
---|
1285 | defaults to the PRIKEY_NBITS class variable setting. This keyword is |
---|
1286 | ignored if a key pair is passed in from an external source via the |
---|
1287 | keyPair keyword |
---|
1288 | |
---|
1289 | @rtype: tuple |
---|
1290 | @return credentials as strings in PEM format: the |
---|
1291 | user certificate, it's private key and the issuing certificate. The |
---|
1292 | issuing certificate is only set if the user certificate is a proxy |
---|
1293 | |
---|
1294 | @type bootstrap: bool |
---|
1295 | @param bootstrap: If set to True, bootstrap trust roots i.e. connect to |
---|
1296 | MyProxy server without verification of the server's SSL certificate |
---|
1297 | against any CA certificates. Set to False, for default behaviour: |
---|
1298 | verify server SSL certificate against CA certificates held in location |
---|
1299 | set by the "caCertDir" attribute. If bootstrap is set, updateTrustRoots |
---|
1300 | will be forced to True also |
---|
1301 | |
---|
1302 | @type updateTrustRoots: bool |
---|
1303 | @param updateTrustRoots: set to True to update the trust roots |
---|
1304 | |
---|
1305 | @type authnGetTrustRootsCall: bool |
---|
1306 | @param authnGetTrustRootsCall: pass username and password to |
---|
1307 | getTrustRoots call. getTrustRoots is invoked if the "updateTrustRoots" |
---|
1308 | or "bootstrap" keywords are set. This is not recommended for |
---|
1309 | bootstrap since in this case the server is NOT authenticated by this |
---|
1310 | client. |
---|
1311 | """ |
---|
1312 | if bootstrap: |
---|
1313 | log.info('Bootstrapping MyProxy server root of trust.') |
---|
1314 | |
---|
1315 | # Bootstrap implies update to trust roots |
---|
1316 | updateTrustRoots = True |
---|
1317 | |
---|
1318 | if updateTrustRoots: |
---|
1319 | if authnGetTrustRootsCall: |
---|
1320 | getTrustRootsKw = { |
---|
1321 | 'username': username, 'passphrase': passphrase |
---|
1322 | } |
---|
1323 | else: |
---|
1324 | getTrustRootsKw = {} |
---|
1325 | |
---|
1326 | self.getTrustRoots(writeToCACertDir=True, |
---|
1327 | bootstrap=bootstrap, |
---|
1328 | **getTrustRootsKw) |
---|
1329 | |
---|
1330 | lifetime = lifetime or self.proxyCertLifetime |
---|
1331 | |
---|
1332 | # Certificate request may be passed as an input but if not generate it |
---|
1333 | # here request here |
---|
1334 | if certReq is None: |
---|
1335 | # If no key pair was passed, generate here |
---|
1336 | if keyPair is None: |
---|
1337 | keyPair = self._createKeyPair(nBitsForKey=nBitsForKey) |
---|
1338 | |
---|
1339 | certReq = self._createCertReq(username, keyPair) |
---|
1340 | |
---|
1341 | if keyPair is not None: |
---|
1342 | pemKeyPair = crypto.dump_privatekey(crypto.FILETYPE_PEM, keyPair) |
---|
1343 | |
---|
1344 | # Set-up SSL connection |
---|
1345 | conn = self._initConnection() |
---|
1346 | conn.connect((self.hostname, self.port)) |
---|
1347 | |
---|
1348 | # send globus compatibility stuff |
---|
1349 | conn.write('0') |
---|
1350 | |
---|
1351 | # send get command - ensure conversion from unicode before writing |
---|
1352 | cmd = MyProxyClient.GET_CMD % (username, passphrase, lifetime) |
---|
1353 | |
---|
1354 | conn.write(str(cmd)) |
---|
1355 | |
---|
1356 | # process server response |
---|
1357 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1358 | |
---|
1359 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
1360 | if respCode: |
---|
1361 | raise MyProxyClientGetError(errorTxt) |
---|
1362 | |
---|
1363 | # Send certificate request |
---|
1364 | conn.send(certReq) |
---|
1365 | |
---|
1366 | # process certificates |
---|
1367 | # - 1st byte , number of certs |
---|
1368 | dat = conn.recv(1) |
---|
1369 | nCerts = ord(dat[0]) |
---|
1370 | |
---|
1371 | # - n certs |
---|
1372 | dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1373 | |
---|
1374 | # Check for error generating certificate |
---|
1375 | try: |
---|
1376 | respCode, errorTxt = self._deserializeResponse(dat) |
---|
1377 | if respCode: |
---|
1378 | raise MyProxyClientGetError(errorTxt) |
---|
1379 | |
---|
1380 | except ValueError: |
---|
1381 | # If all is well, no error response will have been set so an attempt |
---|
1382 | # to parse an error code will fail |
---|
1383 | pass |
---|
1384 | |
---|
1385 | # process server response |
---|
1386 | resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1387 | respCode, errorTxt = self._deserializeResponse(resp) |
---|
1388 | if respCode: |
---|
1389 | raise MyProxyClientRetrieveError(errorTxt) |
---|
1390 | |
---|
1391 | # deserialize certs from received cert data |
---|
1392 | pemCerts = self._deserializeCerts(dat) |
---|
1393 | if len(pemCerts) != nCerts: |
---|
1394 | MyProxyClientRetrieveError("%d certs expected, %d received" % |
---|
1395 | (nCerts, len(pemCerts))) |
---|
1396 | |
---|
1397 | if keyPair is not None: |
---|
1398 | # Return certs and private key |
---|
1399 | # - proxy or dynamically issued certificate (MyProxy CA mode) |
---|
1400 | # - private key |
---|
1401 | # - rest of cert chain if proxy cert issued |
---|
1402 | creds = [pemCerts[0], pemKeyPair] |
---|
1403 | creds.extend(pemCerts[1:]) |
---|
1404 | else: |
---|
1405 | # Key generated externally - return certificate chain only |
---|
1406 | creds = pemCerts |
---|
1407 | |
---|
1408 | |
---|
1409 | return tuple(creds) |
---|
1410 | |
---|
1411 | def getDelegation(self, *arg, **kw): |
---|
1412 | """Retrieve proxy cert for user - same as logon""" |
---|
1413 | return self.logon(*arg, **kw) |
---|
1414 | |
---|
1415 | def getTrustRoots(self, |
---|
1416 | username='', |
---|
1417 | passphrase='', |
---|
1418 | writeToCACertDir=False, |
---|
1419 | bootstrap=False): |
---|
1420 | """Get trust roots for the given MyProxy server |
---|
1421 | |
---|
1422 | @type username: basestring |
---|
1423 | @param username: username (optional) |
---|
1424 | |
---|
1425 | @type passphrase: basestring |
---|
1426 | @param passphrase: pass-phrase (optional) |
---|
1427 | server |
---|
1428 | |
---|
1429 | @type writeToCACertDir: bool |
---|
1430 | @param writeToCACertDir: if set to True, write the retrieved trust roots |
---|
1431 | out to the directory specified by the "caCertDir" attribute |
---|
1432 | |
---|
1433 | @type bootstrap: bool |
---|
1434 | @param bootstrap: If set to True, bootstrap trust roots i.e. connect to |
---|
1435 | MyProxy server without verification of the server's SSL certificate |
---|
1436 | against any CA certificates. Set to False, for default behaviour: |
---|
1437 | verify server SSL certificate against CA certificates held in location |
---|
1438 | set by the "caCertDir" attribute. |
---|
1439 | |
---|
1440 | @return: trust root files as a dictionary keyed by file name with each |
---|
1441 | item value set to the file contents |
---|
1442 | @rtype: dict |
---|
1443 | """ |
---|
1444 | if bootstrap: |
---|
1445 | log.info('Bootstrapping MyProxy server root of trust.') |
---|
1446 | |
---|
1447 | # Set-up SSL connection |
---|
1448 | conn = self._initConnection(verifyPeerWithTrustRoots=(not bootstrap)) |
---|
1449 | conn.connect((self.hostname, self.port)) |
---|
1450 | |
---|
1451 | # send globus compatibility stuff |
---|
1452 | conn.write('0') |
---|
1453 | |
---|
1454 | # send get command - ensure conversion from unicode before writing |
---|
1455 | cmd = MyProxyClient.GET_TRUST_ROOTS_CMD % (username, passphrase) |
---|
1456 | conn.write(str(cmd)) |
---|
1457 | |
---|
1458 | # process server response chunks until all consumed |
---|
1459 | dat = '' |
---|
1460 | tries = 0 |
---|
1461 | try: |
---|
1462 | for tries in range(MyProxyClient.MAX_RECV_TRIES): |
---|
1463 | dat += conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1464 | except SSL.SysCallError: |
---|
1465 | # Expect this exception when response content exhausted |
---|
1466 | pass |
---|
1467 | |
---|
1468 | # Precaution |
---|
1469 | if tries == MyProxyClient.MAX_RECV_TRIES: |
---|
1470 | log.warning('Maximum %d tries reached for getTrustRoots response ' |
---|
1471 | 'block retrieval with block size %d', |
---|
1472 | MyProxyClient.MAX_RECV_TRIES, |
---|
1473 | MyProxyClient.SERVER_RESP_BLK_SIZE) |
---|
1474 | |
---|
1475 | fieldName = MyProxyClient.TRUSTED_CERTS_FIELDNAME |
---|
1476 | prefix = MyProxyClient.TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX |
---|
1477 | respCode, errorTxt, fileData = self._deserializeResponse(dat, |
---|
1478 | fieldName, |
---|
1479 | prefix) |
---|
1480 | if respCode: |
---|
1481 | raise MyProxyClientGetTrustRootsError(errorTxt) |
---|
1482 | |
---|
1483 | filesDict = dict([(k.split(prefix, 1)[1], base64.b64decode(v)) |
---|
1484 | for k, v in fileData.items() if k != fieldName]) |
---|
1485 | |
---|
1486 | if writeToCACertDir: |
---|
1487 | # Create the CA directory path if doesn't already exist |
---|
1488 | try: |
---|
1489 | os.makedirs(self.caCertDir) |
---|
1490 | except OSError, e: |
---|
1491 | # Ignore if the path already exists |
---|
1492 | if e.errno != errno.EEXIST: |
---|
1493 | raise |
---|
1494 | |
---|
1495 | for fileName, fileContents in filesDict.items(): |
---|
1496 | filePath = os.path.join(self.caCertDir, fileName) |
---|
1497 | open(filePath, 'wb').write(fileContents) |
---|
1498 | |
---|
1499 | return filesDict |
---|
1500 | |
---|