""".. MyProxy Client interface
Developed for the NERC DataGrid Project: http://ndg.nerc.ac.uk/
Major re-write of an original class. This updated version implements methods
with SSL calls with PyOpenSSL rather use calls to myproxy client executables as
in the original. This version is adapted and extended from an original
program myproxy_logon by Tom Uram <turam@mcs.anl.gov>
"""
__author__ = "P J Kershaw"
__date__ = "02/06/05"
__copyright__ = "(C) 2010 Science and Technology Facilities Council"
__license__ = """BSD - See LICENSE file in top-level directory
For myproxy_logon see Access Grid Toolkit Public License (AGTPL)
This product includes software developed by and/or derived from the Access
Grid Project (http://www.accessgrid.org) to which the U.S. Government retains
certain rights."""
__contact__ = "Philip.Kershaw@stfc.ac.uk"
__revision__ = '$Id$'
import logging
log = logging.getLogger(__name__)
import sys
import os
import socket
import base64
import re
import traceback
import errno
from OpenSSL import crypto, SSL
from myproxy.utils.openssl import OpenSSLConfig
from myproxy.utils import CaseSensitiveConfigParser
class MyProxyServerSSLCertVerification(object):
"""Check MyProxy server identity. If hostname doesn't match, allow match of
host's Distinguished Name against MYPROXY_SERVER_DN setting"""
DN_LUT = {
'commonName': 'CN',
'organisationalUnitName': 'OU',
'organisation': 'O',
'countryName': 'C',
'emailAddress': 'EMAILADDRESS',
'localityName': 'L',
'stateOrProvinceName': 'ST',
'streetAddress': 'STREET',
'domainComponent': 'DC',
'userid': 'UID'
}
PARSER_RE_STR = '/(%s)=' % '|'.join(DN_LUT.keys() + DN_LUT.values())
PARSER_RE = re.compile(PARSER_RE_STR)
SERVER_CN_PREFIXES = ('host/', 'myproxy/', '')
SERVER_CN_PREFIX = 'host/'
__slots__ = ('__hostname', '__certDN')
def __init__(self, certDN=None, hostname=None):
"""Override parent class __init__ to enable setting of certDN
setting
:type certDN: string
:param certDN: Set the expected Distinguished Name of the MyProxy \
server to avoid errors matching hostnames. This is useful where the hostname \
is not fully qualified
"""
self.__certDN = None
self.__hostname = None
if certDN is not None:
self.certDN = certDN
if hostname is not None:
self.hostname = hostname
def __call__(self, connection, peerCert, errorStatus, errorDepth,
preverifyOK):
"""Verify MyProxy server certificate
:type connection: OpenSSL.SSL.Connection
:param connection: SSL connection object
:type peerCert: basestring
:param peerCert: MyProxy server host certificate as \
OpenSSL.crypto.X509 instance
:type errorStatus: int
:param errorStatus: error status passed from caller. This is the \
value returned by the OpenSSL C function X509_STORE_CTX_get_error(). Look-up \
x509_vfy.h in the OpenSSL source to get the meanings of the different \
codes. PyOpenSSL doesn't help you!
:type errorDepth: int
:param errorDepth: a non-negative integer representing where in the \
certificate chain the error occurred. If it is zero it occured in the \
end entity certificate, one if it is the certificate which signed the \
end entity certificate and so on.
:type preverifyOK: int
:param preverifyOK: the error status - 0 = Error, 1 = OK of the \
current SSL context irrespective of any verification checks done here. If \
this function yields an OK status, it should enforce the preverifyOK value \
so that any error set upstream overrides and is honoured.
:rtype: int
:return: status code - 0/False = Error, 1/True = OK
"""
if peerCert.has_expired():
# Any expired certificate in the chain should result in an error
log.error('Certificate %r in peer certificate chain has expired',
peerCert.get_subject())
return False
elif errorDepth == 0:
# Only interested in DN of last certificate in the chain - this must
# match the expected MyProxy Server DN setting
peerCertSubj = peerCert.get_subject()
peerCertDN = peerCertSubj.get_components()
peerCertDN.sort()
if self.certDN is None:
# Check hostname against peer certificate CN field instead:
if self.hostname is None:
log.error('No "hostname" or "certDN" set to check peer '
'certificate against')
return False
acceptableCNs = [pfx + self.hostname
for pfx in self.__class__.SERVER_CN_PREFIXES]
if peerCertSubj.commonName in acceptableCNs:
return preverifyOK
else:
log.error('Peer certificate CN %r doesn\'t match the '
'expected CN %r', peerCertSubj.commonName,
acceptableCNs)
return False
else:
if peerCertDN == self.certDN:
return preverifyOK
else:
log.error('Peer certificate DN %r doesn\'t match the '
'expected DN %r', peerCertDN, self.certDN)
return False
else:
return preverifyOK
def get_verify_server_cert_func(self):
def verify_server_cert(connection, peerCert, errorStatus, errorDepth,
preverifyOK):
return self.__call__(connection, peerCert, errorStatus,
errorDepth, preverifyOK)
return verify_server_cert
def _getCertDN(self):
return self.__certDN
def _setCertDN(self, val):
if isinstance(val, basestring):
# Allow for quoted DN
certDN = val.strip('"')
dnFields = self.__class__.PARSER_RE.split(certDN)
if len(dnFields) < 2:
raise TypeError('Error parsing DN string: "%s"' % certDN)
self.__certDN = zip(dnFields[1::2], dnFields[2::2])
self.__certDN.sort()
elif not isinstance(val, list):
for i in val:
if not len(i) == 2:
raise TypeError('Expecting list of two element DN field, '
'DN field value pairs for "certDN" '
'attribute')
self.__certDN = val
else:
raise TypeError('Expecting list or string type for "certDN" '
'attribute')
certDN = property(fget=_getCertDN,
fset=_setCertDN,
doc="Distinguished Name for MyProxy Server Certificate")
# Get/Set Property methods
def _getHostname(self):
return self.__hostname
def _setHostname(self, val):
if not isinstance(val, basestring):
raise TypeError("Expecting string type for hostname "
"attribute")
self.__hostname = val
hostname = property(fget=_getHostname,
fset=_setHostname,
doc="hostname of MyProxy server")
class MyProxyClientError(Exception):
"""Base exception class for MyProxyClient exceptions"""
class MyProxyClientConfigError(MyProxyClientError):
"""Error with configuration"""
class MyProxyClientGetError(MyProxyClientError):
"""Exceptions arising from get request to server"""
class MyProxyClientRetrieveError(MyProxyClientError):
"""Error recovering a response from MyProxy"""
class MyProxyCredentialsAlreadyExist(MyProxyClientError):
"""Attempting to upload credentials to the server which already exist. -
See MyProxyClient.store
"""
class MyProxyClientGetTrustRootsError(MyProxyClientError):
"""Error retrieving trust roots"""
[docs]class MyProxyClient(object):
"""MyProxy client interface
Based on protocol definitions in:
http://grid.ncsa.uiuc.edu/myproxy/protocol/
:type SSL_METHOD: string
:cvar SSL_METHOD: encryption method used for connecting to MyProxy server \
- set to TLS version 1
:type MYPROXY_SERVER_ENVVARNAME: string
:cvar MYPROXY_SERVER_ENVVARNAME: server environment variable name
:type MYPROXY_SERVER_PORT_ENVVARNAME: string
:cvar MYPROXY_SERVER_PORT_ENVVARNAME: port environment variable name
:type MYPROXY_SERVER_DN_ENVVARNAME: string
:cvar MYPROXY_SERVER_DN_ENVVARNAME: server certificate Distinguished Name \
environment variable name
:type GLOBUS_LOCATION_ENVVARNAME: string
:cvar GLOBUS_LOCATION_ENVVARNAME: 'GLOBUS_LOCATION' environment variable \
name
:type GET_CMD: string
:cvar GET_CMD: get command string
:type INFO_CMD: string
:cvar INFO_CMD: info command string
:type DESTROY_CMD: string
:cvar DESTROY_CMD: destroy command string
:type CHANGE_PASSPHRASE_CMD: string
:cvar CHANGE_PASSPHRASE_CMD: command string to change cred pass-phrase
:type STORE_CMD: string
:cvar STORE_CMD: store command string
:type GET_TRUST_ROOTS_CMD: string
:cvar GET_TRUST_ROOTS_CMD: get trust roots command string
:type TRUSTED_CERTS_FIELDNAME: string
:cvar TRUSTED_CERTS_FIELDNAME: field name in get trust roots response \
for trusted certificate file names
:type TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX: string
:cvar TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX: field name prefix in get \
trust roots response for trusted certificate file contents
:type HOSTCERT_SUBDIRPATH: string
:cvar HOSTCERT_SUBDIRPATH: sub-directory path host certificate (as tuple)
:type HOSTKEY_SUBDIRPATH: string
:cvar HOSTKEY_SUBDIRPATH: sub-directory path to host key (as tuple)
:type PRIKEY_NBITS: int
:cvar PRIKEY_NBITS: default number of bits for private key generated
:type MESSAGE_DIGEST_TYPE: string
:cvar MESSAGE_DIGEST_TYPE: message digest type is MD5
:type SERVER_RESP_BLK_SIZE: int
:cvar SERVER_RESP_BLK_SIZE: block size for retrievals from server
:type MAX_RECV_TRIES: int
:cvar MAX_RECV_TRIES: maximum number of retrievals of size \
SERVER_RESP_BLK_SIZE before this client gives up
:type DEF_PROXY_FILEPATH: string
:cvar DEF_PROXY_FILEPATH: default location for proxy file to be written to
:type PROXY_FILE_PERMISSIONS: int
:cvar PROXY_FILE_PERMISSIONS: file permissions returned proxy file is \
created with
:type PROPERTY_DEFAULTS: tuple
:cvar PROPERTY_DEFAULTS: sets permissible element names for MyProxy config \
file
:type ROOT_USERNAME: string
:cvar ROOT_USERNAME: root username - used to determine output directory \
for trust roots
:type ROOT_TRUSTROOT_DIR: string
:cvar ROOT_TRUSTROOT_DIR: default trust root directory if running as root \
user
:type USER_TRUSTROOT_DIR: string
:cvar USER_TRUSTROOT_DIR: default trust root directory for users other \
than root
:type X509_CERT_DIR_ENVVARNAME: string
:cvar X509_CERT_DIR_ENVVARNAME: environment variable name 'X509_CERT_DIR', \
which if set points to the location of the trust roots
:type X509_USER_PROXY_ENVVARNAME: string
:cvar X509_USER_PROXY_ENVVARNAME: environment variable name \
'X509_USER_PROXY' if set points to the output location of the output EEC / \
Proxy certificate. Not currently used by this class, included for \
reference only
"""
# Parametise SSL METHOD to allow later update if needed - set to TLSv1 to
# address POODLE vulnerability
SSL_METHOD = SSL.TLSv1_METHOD
MYPROXY_SERVER_ENVVARNAME = 'MYPROXY_SERVER'
MYPROXY_SERVER_PORT_ENVVARNAME = 'MYPROXY_SERVER_PORT'
MYPROXY_SERVER_DN_ENVVARNAME = 'MYPROXY_SERVER_DN'
GLOBUS_LOCATION_ENVVARNAME = 'GLOBUS_LOCATION'
X509_USER_PROXY_ENVVARNAME = 'X509_USER_PROXY'
GET_CMD="""VERSION=MYPROXYv2
COMMAND=0
USERNAME=%s
PASSPHRASE=%s
LIFETIME=%d"""
PUT_CMD="""VERSION=MYPROXYv2
COMMAND=1
USERNAME=%s
PASSPHRASE=<pass phrase>
LIFETIME=%d"""
INFO_CMD="""VERSION=MYPROXYv2
COMMAND=2
USERNAME=%s
PASSPHRASE=PASSPHRASE
LIFETIME=0"""
DESTROY_CMD="""VERSION=MYPROXYv2
COMMAND=3
USERNAME=%s
PASSPHRASE=PASSPHRASE
LIFETIME=0"""
CHANGE_PASSPHRASE_CMD="""VERSION=MYPROXYv2
COMMAND=4
USERNAME=%s
PASSPHRASE=%s
NEW_PHRASE=%s
LIFETIME=0"""
STORE_CMD="""VERSION=MYPROXYv2
COMMAND=5
USERNAME=%s
PASSPHRASE=
LIFETIME=%d"""
GET_TRUST_ROOTS_CMD="""VERSION=MYPROXYv2
COMMAND=7
USERNAME=%s
PASSPHRASE=%s
LIFETIME=0
TRUSTED_CERTS=1"""
TRUSTED_CERTS_FIELDNAME = 'TRUSTED_CERTS'
TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX = 'FILEDATA_'
HOSTCERT_FILENAME = 'hostcert.pem'
HOSTKEY_FILENAME = 'hostkey.pem'
GRID_SECURITY_DIRPATH = '/etc/grid-security'
HOSTCERT_GRID_FILEPATH = os.path.join(GRID_SECURITY_DIRPATH,
HOSTCERT_FILENAME)
HOSTKEY_GRID_FILEPATH = os.path.join(GRID_SECURITY_DIRPATH,
HOSTKEY_FILENAME)
HOSTCERT_SUBDIRPATH = ('etc', HOSTCERT_FILENAME)
HOSTKEY_SUBDIRPATH = ('etc', HOSTKEY_FILENAME)
PROXY_FILE_PERMISSIONS = 0600
# Work out default location of proxy file if it exists. This is set if a
# call has been made previously to logon / get-delegation
DEF_PROXY_FILEPATH = sys.platform == ('win32' and 'proxy' or
sys.platform in ('linux2', 'darwin') and
'/tmp/x509up_u%s' % (os.getuid())
or None)
PRIKEY_NBITS = 4096
MESSAGE_DIGEST_TYPE = "md5"
SERVER_RESP_BLK_SIZE = 8192
MAX_RECV_TRIES = 1024
# valid configuration property keywords
PROPERTY_DEFAULTS = {
'hostname': 'localhost',
'port': 7512,
'serverDN': None,
'openSSLConfFilePath': '',
'proxyCertMaxLifetime': 43200,
'proxyCertLifetime': 43200,
'caCertDir': None
}
ROOT_USERNAME = 'root'
ROOT_TRUSTROOT_DIR = '/etc/grid-security/certificates'
USER_TRUSTROOT_DIR = '~/.globus/certificates'
X509_CERT_DIR_ENVVARNAME = 'X509_CERT_DIR'
X509_USER_PROXY_ENVVARNAME = 'X509_USER_PROXY'
# Restrict attributes to the above properties, their equivalent
# protected values + extra OpenSSL config object.
__slots__ = tuple(['__' + k for k in PROPERTY_DEFAULTS.keys()])
del k
__slots__ += ('__openSSLConfig', '__cfg', '__ssl_verification')
def __init__(self, cfgFilePath=None, **prop):
"""Make any initial settings for client connections to MyProxy
Settings are held in a dictionary which can be set from **prop,
a call to setProperties() or by passing settings in an XML file
given by cfgFilePath
:param cfgFilePath: set properties via a configuration file
:type cfgFilePath: basestring
:param **prop: set properties via keywords - see
PROPERTY_DEFAULTS class variable for a list of these
:type **prop: dict
"""
# Altered method here for setting verification function. The previous
# mechanism using the classes __call__ method stopped working with
# PyOpenSSL version 0.14. The new approach is to return a copy of a
# wrapper function
#
# P J Kershaw 09/12/14
self.__ssl_verification = MyProxyServerSSLCertVerification()
self.__hostname = None
self.__port = None
self.__serverDN = None
self.__openSSLConfFilePath = None
self.__proxyCertMaxLifetime = MyProxyClient.PROPERTY_DEFAULTS[
'proxyCertMaxLifetime']
self.__proxyCertLifetime = MyProxyClient.PROPERTY_DEFAULTS[
'proxyCertLifetime']
self.__caCertDir = None
self.__cfg = None
# Configuration file used to get default subject when generating a
# new proxy certificate request
self.__openSSLConfig = OpenSSLConfig()
# Server host name - take from environment variable if available
self.hostname = os.environ.get(MyProxyClient.MYPROXY_SERVER_ENVVARNAME,
MyProxyClient.PROPERTY_DEFAULTS['hostname'])
# ... and port number
self.port = int(os.environ.get(
MyProxyClient.MYPROXY_SERVER_PORT_ENVVARNAME,
MyProxyClient.PROPERTY_DEFAULTS['port']))
# Server Distinguished Name
serverDN = os.environ.get(MyProxyClient.MYPROXY_SERVER_DN_ENVVARNAME,
MyProxyClient.PROPERTY_DEFAULTS['serverDN'])
if serverDN is not None:
self.serverDN = serverDN
# Set trust root - the directory containing the CA certificates for
# verifying the MyProxy server's SSL certificate
self.setDefaultCACertDir()
# Any keyword settings override the defaults above
for opt, val in prop.items():
setattr(self, opt, val)
# If properties file is set any parameters settings in file will
# override those set by input keyword or the defaults
if cfgFilePath is not None:
self.parseConfig(cfg=cfgFilePath)
[docs] def setDefaultCACertDir(self):
'''Make default trust root setting - the directory containing the CA
certificates for verifying the MyProxy server's SSL certificate.
The setting is made by using standard Globus defined locations and
environment variable settings
'''
# Check for X509_CERT_DIR environment variable
x509CertDir = os.environ.get(MyProxyClient.X509_CERT_DIR_ENVVARNAME)
if x509CertDir is not None:
self.caCertDir = x509CertDir
# Check for running as root user
elif os.environ.get(MyProxyClient.ROOT_USERNAME) is not None:
self.caCertDir = MyProxyClient.ROOT_TRUSTROOT_DIR
# Default to non-root standard location
else:
self.caCertDir = os.path.expanduser(
MyProxyClient.USER_TRUSTROOT_DIR)
def _get_ssl_verification(self):
return self.__ssl_verification
def _set_ssl_verification(self, value):
if not isinstance(value, MyProxyServerSSLCertVerification):
raise TypeError('Expecting %r derived type for '
'"ssl_verification" attribute; got %r' %
MyProxyServerSSLCertVerification,
value)
self.__ssl_verification = value
ssl_verification = property(_get_ssl_verification,
_set_ssl_verification,
doc="Class with a method which is passed to "
"the SSL context to verify the peer "
"(MyProxy server) certificate in the SSL "
"handshake between this client and the "
"MyProxy server")
[docs] def parseConfig(self, cfg, section='DEFAULT'):
'''Extract parameters from _cfg config object'''
if isinstance(cfg, basestring):
cfgFilePath = os.path.expandvars(cfg)
self.__cfg = CaseSensitiveConfigParser()
self.__cfg.read(cfgFilePath)
else:
cfgFilePath = None
self.__cfg = cfg
for key, val in self.__cfg.items(section):
setattr(self, key, val)
# Get/Set Property methods
def _getHostname(self):
return self.__hostname
def _setHostname(self, val):
"""Also sets SSL Certificate verification object property!"""
if not isinstance(val, basestring):
raise TypeError("Expecting string type for hostname attribute")
self.__hostname = val
self.__ssl_verification.hostname = val
hostname = property(fget=_getHostname,
fset=_setHostname,
doc="hostname of MyProxy server")
def _getPort(self):
return self.__port
def _setPort(self, val):
if isinstance(val, basestring):
self.__port = int(val)
elif isinstance(val, int):
self.__port = val
else:
raise TypeError("Expecting int type for port attribute")
port = property(fget=_getPort,
fset=_setPort,
doc="Port number for MyProxy server")
def _getServerDN(self):
return self.__serverDN
def _setServerDN(self, val):
"""Also sets SSL Certificate verification object property!"""
if not isinstance(val, basestring):
raise TypeError("Expecting string type for serverDN attribute")
self.__serverDN = val
self.__ssl_verification.certDN = val
serverDN = property(fget=_getServerDN,
fset=_setServerDN,
doc="Distinguished Name for MyProxy Server "
"Certificate")
def _getOpenSSLConfFilePath(self):
return self.__openSSLConfFilePath
def _setOpenSSLConfFilePath(self, val):
if not isinstance(val, basestring):
raise TypeError('Expecting string type for "openSSLConfFilePath" '
'attribute')
self.__openSSLConfFilePath = os.path.expandvars(val)
self.__openSSLConfig.filePath = self.__openSSLConfFilePath
self.__openSSLConfig.read()
openSSLConfFilePath = property(fget=_getOpenSSLConfFilePath,
fset=_setOpenSSLConfFilePath,
doc="file path for OpenSSL config file")
def _getProxyCertMaxLifetime(self):
return self.__proxyCertMaxLifetime
def _setProxyCertMaxLifetime(self, val):
if isinstance(val, basestring):
self.__proxyCertMaxLifetime = int(val)
elif isinstance(val, int):
self.__proxyCertMaxLifetime = val
else:
raise TypeError("Expecting int type for proxyCertMaxLifetime "
"attribute")
proxyCertMaxLifetime = property(fget=_getProxyCertMaxLifetime,
fset=_setProxyCertMaxLifetime,
doc="Default max. lifetime allowed for "
"Proxy Certificate retrieved - used "
"by store method")
def _getProxyCertLifetime(self):
return self.__proxyCertLifetime
def _setProxyCertLifetime(self, val):
if isinstance(val, (basestring, float)):
self.__proxyCertLifetime = int(val)
elif isinstance(val, int):
self.__proxyCertLifetime = val
else:
raise TypeError("Expecting int, float or string type for input "
"proxyCertLifetime attribute")
proxyCertLifetime = property(fget=_getProxyCertLifetime,
fset=_setProxyCertLifetime,
doc="Default proxy cert. lifetime (seconds) "
"used in logon request")
def _getCACertDir(self):
return self.__caCertDir
def _setCACertDir(self, val):
'''Specify a directory containing PEM encoded CA certs. used for
validation of MyProxy server certificate.
Set to None to make OpenSSL.SSL.Context.load_verify_locations ignore
this parameter
:type val: basestring/None
:param val: directory path'''
if isinstance(val, basestring):
if val == '':
self.__caCertDir = None
else:
self.__caCertDir = os.path.expandvars(val)
elif isinstance(val, None):
self.__caCertDir = val
else:
raise TypeError("Expecting string or None type for caCertDir "
"attribute")
caCertDir = property(fget=_getCACertDir,
fset=_setCACertDir,
doc="trust roots directory containing PEM encoded CA "
"certificates to validate MyProxy server "
"certificate")
def _getOpenSSLConfig(self):
"Get OpenSSLConfig object property method"
return self.__openSSLConfig
openSSLConfig = property(fget=_getOpenSSLConfig, doc="OpenSSLConfig object")
def _initConnection(self,
certFile=None,
keyFile=None,
keyFilePassphrase=None,
verifyPeerWithTrustRoots=True):
"""Initialise connection setting up SSL context and client and
server side identity checks
:type sslCertFile: basestring
:param sslCertFile: certificate for SSL client authentication. It may \
be owner of a credential to be acted on or the concatenated proxy \
certificate + proxy's signing cert. SSL client authentication is not \
necessary for getDelegation / logon calls
:type sslKeyFile: basestring
:param sslKeyFile: client private key file
:type keyFilePassphrase: basestring
:param keyFilePassphrase: pass-phrase protecting private key if set \
:type verifyPeerWithTrustRoots: bool
:param verifyPeerWithTrustRoots: verify MyProxy server's SSL certificate \
against a list of trusted CA certificates in the CA certificate \
directory set by the "CaCertDir" attribute. This should always be set \
to True for MyProxy client calls unless using the 'bootstrap' trust \
roots mode available with logon and get trust roots calls
"""
context = SSL.Context(self.__class__.SSL_METHOD)
if verifyPeerWithTrustRoots:
context.load_verify_locations(None, self.caCertDir)
self.__ssl_verification.hostname = self.hostname
verify_cb = self.__ssl_verification.get_verify_server_cert_func()
# Verify peer's (MyProxy server) certificate
context.set_verify(SSL.VERIFY_PEER, verify_cb)
if certFile:
try:
context.use_certificate_chain_file(certFile)
def pwdCallback(passphraseMaxLen,
promptPassphraseTwice,
passphrase):
"""Private key file password callback function"""
if len(passphrase) > passphraseMaxLen:
log.error('Passphrase length %d is greater than the '
'maximum length allowed %d',
len(passphrase), passphraseMaxLen)
return ''
return passphrase
if keyFilePassphrase is not None:
context.set_passwd_cb(pwdCallback, keyFilePassphrase)
context.use_privatekey_file(keyFile)
except Exception:
raise MyProxyClientConfigError("Loading certificate "
"and private key for SSL "
"connection [also check CA "
"certificate settings]: %s" %
traceback.format_exc())
# Disable for compatibility with myproxy server (er, globus)
# globus doesn't handle this case, apparently, and instead
# chokes in proxy delegation code
context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
# connect to myproxy server
conn = SSL.Connection(context, socket.socket())
return conn
def _createKeyPair(self, nBitsForKey=PRIKEY_NBITS):
"""Generate key pair and return as PEM encoded string
:type nBitsForKey: int
:param nBitsForKey: number of bits for private key generation - \
default is 2048
:rtype: OpenSSL.crypto.PKey
:return: public/private key pair
"""
keyPair = crypto.PKey()
keyPair.generate_key(crypto.TYPE_RSA, nBitsForKey)
return keyPair
def _createCertReq(self, CN, keyPair, messageDigest=MESSAGE_DIGEST_TYPE):
"""Create a certificate request.
:type CN: basestring
:param CN: Common Name for certificate - effectively the same as the \
username for the MyProxy credential
:type keyPair: string/None
:param keyPair: public/private key pair
:type messageDigest: basestring
:param messageDigest: message digest type - default is MD5
:rtype: base string
:return certificate request PEM text and private key PEM text
"""
# Check all required certifcate request DN parameters are set
# Create certificate request
certReq = crypto.X509Req()
# Create public key object
certReq.set_pubkey(keyPair)
# Add the public key to the request
certReq.sign(keyPair, messageDigest)
derCertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1,
certReq)
return derCertReq
def _deserializeResponse(self, msg, *fieldNames):
"""
Deserialize a MyProxy server response
:param msg: string response message from MyProxy server
:return: tuple of integer response and errorTxt string (if any) and \
all the fields parsed. fields is a list of two element, field name, field \
value tuples.
:rtype: tuple
"""
lines = msg.split('\n')
fields = [tuple(line.split('=', 1)) for line in lines][:-1]
# get response value
respCode = [int(v) for k, v in fields if k == 'RESPONSE'][0]
# get error text
errorTxt = os.linesep.join([v for k, v in fields if k == 'ERROR'])
# Check for custom fields requested by caller to this method
if fieldNames:
fieldsDict = {}
for k, v in fields:
names = [name for name in fieldNames if k.startswith(name)]
if len(names) == 0:
continue
else:
if v.isdigit():
fieldsDict[k] = int(v)
else:
fieldsDict[k] = v
# Return additional dict item in tuple
return respCode, errorTxt, fieldsDict
else:
return respCode, errorTxt
def _deserializeCerts(self, inputDat):
"""Unpack certificates returned from a get delegation call to the
server
:param inputDat: string containing the proxy cert and private key
and signing cert all in DER format
:return: list containing the equivalent to the input in PEM format"""
pemCerts = []
dat = inputDat
while dat:
# find start of cert, get length
ind = dat.find('\x30\x82')
if ind < 0:
break
length = 256*ord(dat[ind+2]) + ord(dat[ind+3])
# extract der-format cert, and convert to pem
derCert = dat[ind:ind+length+4]
x509Cert = crypto.load_certificate(crypto.FILETYPE_ASN1, derCert)
pemCert = crypto.dump_certificate(crypto.FILETYPE_PEM, x509Cert)
pemCerts.append(pemCert)
# trim cert from data
dat = dat[ind + length + 4:]
return pemCerts
@classmethod
[docs] def locateClientCredentials(cls, enableTmpFileLoc=False):
"""Find the location of a client certificate and private key to use to
authenticate with the server based on the various default locations
that MyProxy/Globus support
:param enableTmpFileLoc: enable setting based on /tmp/x509up_<uid>, \
defaults to False
:type enableTmpFileLoc: bool
:return: private key and certificate file location to use based on the \
current environment
:rtype: tuple
"""
sslKeyFile = None
sslCertFile = None
x509UserProxy = os.environ.get(cls.X509_USER_PROXY_ENVVARNAME)
if x509UserProxy is not None:
sslKeyFile = x509UserProxy
sslCertFile = x509UserProxy
elif enableTmpFileLoc and os.access(cls.DEF_PROXY_FILEPATH, os.R_OK):
sslKeyFile = cls.DEF_PROXY_FILEPATH
sslCertFile = cls.DEF_PROXY_FILEPATH
elif (os.access(cls.HOSTKEY_GRID_FILEPATH, os.R_OK) and
os.access(cls.HOSTCERT_GRID_FILEPATH, os.R_OK)):
sslKeyFile = cls.HOSTKEY_GRID_FILEPATH
sslCertFile = cls.HOSTCERT_GRID_FILEPATH
else:
globusLoc = os.environ.get(cls.GLOBUS_LOCATION_ENVVARNAME)
if globusLoc:
sslKeyFile = os.path.join(globusLoc,
*cls.HOSTKEY_SUBDIRPATH)
if os.access(sslKeyFile, os.R_OK):
sslCertFile = os.path.join(globusLoc,
*cls.HOSTCERT_SUBDIRPATH)
else:
# Access to the private key is prohibited default to
# username/password based authentication
sslKeyFile = None
sslCertFile = None
return sslKeyFile, sslCertFile
@classmethod
[docs] def writeProxyFile(cls, proxyCert, proxyPriKey, userX509Cert,
filePath=None):
"""Write out proxy cert to file in the same way as myproxy-logon -
proxy cert, private key, user cert. Nb. output from logon can be
passed direct into this method
:type proxyCert: string
:param proxyCert: proxy certificate
:type proxyPriKey: string
:param proxyPriKey: private key for proxy
:type userX509Cert: string
:param userX509Cert: user certificate which issued the proxy
:type filePath: string
:param filePath: set to override the default filePath"""
if filePath is None:
filePath = MyProxyClient.DEF_PROXY_FILEPATH
if filePath is None:
MyProxyClientConfigError("Error setting proxy file path - invalid "
"platform?")
outStr = proxyCert + proxyPriKey + userX509Cert
open(filePath, 'w').write(outStr)
try:
# Make sure permissions are set correctly
os.chmod(filePath, MyProxyClient.PROXY_FILE_PERMISSIONS)
except OSError, e:
# Don't leave the file lying around if couldn't change its
# permissions
os.unlink(filePath)
log.error('Unable to set %o permissions for proxy file "%s": %s',
MyProxyClient.PROXY_FILE_PERMISSIONS, filePath, e)
raise
@classmethod
[docs] def readProxyFile(cls, filePath=None):
"""Read proxy cert file following the format used by myproxy-logon -
proxy, cert, private key, user cert.
:rtype: tuple
:return: tuple containing proxy cert, private key, user cert"""
if filePath is None:
filePath = MyProxyClient.DEF_PROXY_FILEPATH
if filePath is None:
MyProxyClientConfigError("Error setting proxy file path - invalid "
"platform?")
proxy = open(filePath).read()
# Split certs and key into separate tuple items
return tuple(['-----BEGIN'+i for i in proxy.split('-----BEGIN')[1:]])
[docs] def put(self,
username,
passphrase,
userCertFile,
userKeyFile,
lifetime=None,
sslCertFile=None,
sslKeyFile=None,
sslKeyFilePassphrase=None):
"""Store a proxy credential on the server
Unfortunately this method is not implemented as it requires the creation
of a proxy certificate by the client but PyOpenSSL doesn't currently
support the required proxyCertInfo X.509 certificate extension
:raise NotImplementedError: see above
:type username: string
:param username: username selected for new credential
:type passphrase: string
:param passphrase: pass-phrase for new credential. This will be used \
by the server to authenticate later requests. It must be at least \
6 characters. The server may impose other restrictions too depending \
on its configuration.
:type certFile: string
:param certFile: user's X.509 proxy certificate in PEM format
:type keyFile: string
:param keyFile: equivalent private key file in PEM format
:type sslCertFile: string
:param sslCertFile: certificate used for client authentication with \
the MyProxy server SSL connection. If not set, \
this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
:type sslKeyFile: string
:param sslKeyFile: corresponding private key file. See explanation \
for sslCertFile
:type sslKeyFilePassphrase: string
:param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the \
private key is not password protected.
:type lifetime: int / None
:param lifetime: the maximum lifetime allowed for retrieved proxy \
credentials in seconds. defaults to proxyCertMaxLifetime attribute value
"""
raise NotImplementedError('put method is not currently implemented. '
'It requires the creation of a proxy '
'certificate by the client but PyOpenSSL '
'doesn\'t currently support the required '
'proxyCertInfo X.509 certificate extension.')
[docs] def info(self,
username,
sslCertFile=None,
sslKeyFile=None,
sslKeyFilePassphrase=None):
"""return True/False whether credentials exist on the server for a
given username
:raise MyProxyClientGetError:
:raise MyProxyClientRetrieveError:
:type username: string
:param username: username selected for credential
:type sslCertFile: string
:param sslCertFile: certificate used for client authentication with \
the MyProxy server SSL connection. This ID will be set as the owner of the \
stored credentials. Only the owner can later remove credentials with \
myproxy-destroy or the destroy method. If not set, this argument defaults to \
$GLOBUS_LOCATION/etc/hostcert.pem
:type sslKeyFile: string
:param sslKeyFile: corresponding private key file. See explanation \
for sslCertFile
:type sslKeyFilePassphrase: string
:param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the \
private key is not password protected.
"""
globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
if not sslCertFile:
if globusLoc:
sslCertFile = os.path.join(globusLoc,
*MyProxyClient.HOSTCERT_SUBDIRPATH)
sslKeyFile = os.path.join(globusLoc,
*MyProxyClient.HOSTKEY_SUBDIRPATH)
else:
raise MyProxyClientError(
"No client authentication cert. and private key file were given")
# Set-up SSL connection
conn = self._initConnection(certFile=sslCertFile,
keyFile=sslKeyFile,
keyFilePassphrase=sslKeyFilePassphrase)
conn.connect((self.hostname, self.port))
# send globus compatibility stuff
conn.write('0')
# send info command - ensure conversion from unicode before writing
cmd = MyProxyClient.INFO_CMD % username
conn.write(str(cmd))
# process server response
dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
# Pass in the names of fields to return in the dictionary 'field'
respCode, errorTxt, field = self._deserializeResponse(dat,
'CRED_START_TIME',
'CRED_END_TIME',
'CRED_OWNER')
return not bool(respCode), errorTxt, field
[docs] def changePassphrase(self,
username,
passphrase,
newPassphrase,
sslCertFile=None,
sslKeyFile=None,
sslKeyFilePassphrase=None):
"""change pass-phrase protecting the credentials for a given username
:raise MyProxyClientGetError:
:raise MyProxyClientRetrieveError:
:param username: username of credential
:param passphrase: existing pass-phrase for credential
:param newPassphrase: new pass-phrase to replace the existing one.
:param sslCertFile: certificate used for client authentication with \
the MyProxy server SSL connection. This ID will be set as the owner \
of the stored credentials. Only the owner can later remove \
credentials with myproxy-destroy or the destroy method. If not set, \
this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
:param sslKeyFile: corresponding private key file. See explanation \
for sslCertFile
:param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the \
private key is not password protected.
:return: none
"""
globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
if not sslCertFile or not sslKeyFile:
if globusLoc:
sslCertFile = os.path.join(globusLoc,
*MyProxyClient.HOSTCERT_SUBDIRPATH)
sslKeyFile = os.path.join(globusLoc,
*MyProxyClient.HOSTKEY_SUBDIRPATH)
else:
raise MyProxyClientError(
"No client authentication cert. and private key file were given")
# Set-up SSL connection
conn = self._initConnection(certFile=sslCertFile,
keyFile=sslKeyFile,
keyFilePassphrase=sslKeyFilePassphrase)
conn.connect((self.hostname, self.port))
# send globus compatibility stuff
conn.write('0')
# send command - ensure conversion from unicode before writing
cmd = MyProxyClient.CHANGE_PASSPHRASE_CMD % (username,
passphrase,
newPassphrase)
conn.write(str(cmd))
# process server response
dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
respCode, errorTxt = self._deserializeResponse(dat)
if respCode:
raise MyProxyClientGetError(errorTxt)
[docs] def destroy(self,
username,
sslCertFile=None,
sslKeyFile=None,
sslKeyFilePassphrase=None):
"""destroy credentials from the server for a given username
:raise MyProxyClientGetError:
:raise MyProxyClientRetrieveError:
:param username: username selected for credential
:param sslCertFile: certificate used for client authentication with \
the MyProxy server SSL connection. This ID will be set as the owner \
of the stored credentials. Only the owner can later remove \
credentials with myproxy-destroy or the destroy method. If not set, \
this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
:param sslKeyFile: corresponding private key file. See explanation \
for sslCertFile
:param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the \
private key is not password protected.
:return: none
"""
globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
if not sslCertFile or not sslKeyFile:
if globusLoc:
sslCertFile = os.path.join(globusLoc,
*MyProxyClient.HOSTCERT_SUBDIRPATH)
sslKeyFile = os.path.join(globusLoc,
*MyProxyClient.HOSTKEY_SUBDIRPATH)
else:
raise MyProxyClientError(
"No client authentication cert. and private key file were given")
# Set-up SSL connection
conn = self._initConnection(certFile=sslCertFile,
keyFile=sslKeyFile,
keyFilePassphrase=sslKeyFilePassphrase)
conn.connect((self.hostname, self.port))
# send globus compatibility stuff
conn.write('0')
# send destroy command - ensure conversion from unicode before writing
cmd = MyProxyClient.DESTROY_CMD % username
conn.write(str(cmd))
# process server response
dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
respCode, errorTxt = self._deserializeResponse(dat)
if respCode:
raise MyProxyClientGetError(errorTxt)
[docs] def store(self,
username,
passphrase,
certFile,
keyFile,
sslCertFile=None,
sslKeyFile=None,
sslKeyFilePassphrase=None,
lifetime=None,
force=True):
"""Upload credentials to the server
:raise MyProxyClientGetError:
:raise MyProxyClientRetrieveError:
:type username: string
:param username: username selected for new credential
:type passphrase: string
:param passphrase: pass-phrase for new credential. This is the pass \
phrase which protects keyfile.
:type certFile: string
:param certFile: user's X.509 certificate in PEM format
:type keyFile: string
:param keyFile: equivalent private key file in PEM format
:type sslCertFile: string
:param sslCertFile: certificate used for client authentication with \
the MyProxy server SSL connection. This ID will be set as the owner \
of the stored credentials. Only the owner can later remove \
credentials with myproxy-destroy or the destroy method. If not set, \
this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this \
is not set, certFile
:type sslKeyFile: string
:param sslKeyFile: corresponding private key file. See explanation \
for sslCertFile
:type sslKeyFilePassphrase: string
:param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the \
private key is not password protected. Nb. keyFile is expected to \
be passphrase protected as this will be the passphrase used for \
logon / getDelegation.
:type Force: bool
:param force: set to True to overwrite any existing creds with the \
same username. If, force=False a check is made with a call to info. \
If creds already, exist exit without proceeding
"""
lifetime = lifetime or self.proxyCertMaxLifetime
# Inputs must be string type otherwise server will reject the request
if isinstance(username, unicode):
username = str(username)
if isinstance(passphrase, unicode):
passphrase = str(passphrase)
globusLoc = os.environ.get(MyProxyClient.GLOBUS_LOCATION_ENVVARNAME)
if not sslCertFile or not sslKeyFile:
if globusLoc:
sslCertFile = os.path.join(globusLoc,
*MyProxyClient.HOSTCERT_SUBDIRPATH)
sslKeyFile = os.path.join(globusLoc,
*MyProxyClient.HOSTKEY_SUBDIRPATH)
else:
# Default so that the owner is the same as the ID of the
# credentials to be uploaded.
sslCertFile = certFile
sslKeyFile = keyFile
sslKeyFilePassphrase = passphrase
if not force:
# Check credentials don't already exist
if self.info(username,
sslCertFile=sslCertFile,
sslKeyFile=sslKeyFile,
sslKeyFilePassphrase=sslKeyFilePassphrase)[0]:
raise MyProxyCredentialsAlreadyExist(
"Credentials already exist for user: %s" % username)
# Set up SSL connection
conn = self._initConnection(certFile=sslCertFile,
keyFile=sslKeyFile,
keyFilePassphrase=sslKeyFilePassphrase)
conn.connect((self.hostname, self.port))
# send globus compatibility stuff
conn.write('0')
# send store command - ensure conversion from unicode before writing
cmd = MyProxyClient.STORE_CMD % (username, lifetime)
conn.write(str(cmd))
# process server response
dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
respCode, errorTxt = self._deserializeResponse(dat)
if respCode:
raise MyProxyClientGetError(errorTxt)
# Send certificate and private key
certTxt = open(certFile).read()
keyTxt = open(keyFile).read()
conn.send(certTxt + keyTxt)
# process server response
resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
respCode, errorTxt = self._deserializeResponse(resp)
if respCode:
raise MyProxyClientRetrieveError(errorTxt)
[docs] def logon(self,
username,
passphrase,
credname=None,
lifetime=None,
keyPair=None,
certReq=None,
nBitsForKey=PRIKEY_NBITS,
bootstrap=False,
updateTrustRoots=False,
authnGetTrustRootsCall=False,
sslCertFile=None,
sslKeyFile=None,
sslKeyFilePassphrase=None):
"""Retrieve a proxy credential from a MyProxy server
Exceptions: MyProxyClientGetError, MyProxyClientRetrieveError
:type username: basestring
:param username: username of credential
:type passphrase: basestring
:param passphrase: pass-phrase for private key of credential held on \
server
:type credname: string / None type
:param credname: optional credential name - provides additional means \
to specify credential to be retrieved
:type lifetime: int
:param lifetime: lifetime for generated certificate
:type keyPair: OpenSSL.crypto.PKey
:param keyPair: Public/Private key pair. This is ignored if a \
certificate request is passed via the certReq keyword
:type certReq: string
:param certReq: ASN1 format certificate request, if none set, one is \
created along with a key pair
:type nBitsForKey: int
:param nBitsForKey: number of bits to use when generating key pair, \
defaults to the PRIKEY_NBITS class variable setting. This keyword is \
ignored if a key pair is passed in from an external source via the keyPair \
keyword
:rtype: tuple
:return: credentials as strings in PEM format: the user certificate, \
its private key and the issuing certificate. The issuing certificate is only \
set if the user certificate is a proxy
:type bootstrap: bool
:param bootstrap: If set to True, bootstrap trust roots i.e. connect \
to MyProxy server without verification of the server's SSL certificate \
against any CA certificates. Set to False, for default behaviour: \
verify server SSL certificate against CA certificates held in location \
set by the "caCertDir" attribute. If bootstrap is set, updateTrustRoots \
will be forced to True also
:type updateTrustRoots: bool
:param updateTrustRoots: set to True to update the trust roots
:type authnGetTrustRootsCall: bool
:param authnGetTrustRootsCall: pass username and password to \
getTrustRoots call. getTrustRoots is invoked if the \
"updateTrustRoots" or "bootstrap" keywords are set. This is not recommended \
for bootstrap since in this case the server is NOT authenticated by this \
client.
:param sslCertFile: applies to SSL client based authentication - \
alternative to username/pass-phrase based. This certificate is used for \
authentication with MyProxy server over the SSL connection. If not set, \
this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
:param sslKeyFile: corresponding private key file. See explanation \
for sslCertFile
:param sslKeyFilePassphrase: passphrase for sslKeyFile. Omit if the \
private key is not password protected.
"""
if bootstrap:
log.info('Bootstrapping MyProxy server root of trust.')
# Bootstrap implies update to trust roots
updateTrustRoots = True
if updateTrustRoots:
if authnGetTrustRootsCall:
getTrustRootsKw = {
'username': username, 'passphrase': passphrase
}
else:
getTrustRootsKw = {}
self.getTrustRoots(writeToCACertDir=True,
bootstrap=bootstrap,
**getTrustRootsKw)
lifetime = lifetime or self.proxyCertLifetime
# Basic sanity check on username to avoid overhead on server
if not username:
raise MyProxyClientGetError('No username has been set')
# Sanitise password - None is legal for modes where username/password
# based authentication is not required
if passphrase is None:
passphrase = ''
# Check for credential name setting
if credname:
if not isinstance(credname, basestring):
raise TypeError('Expecting string type or None for "credname" '
'input argument')
# Make a concatenated username credential parameter to pass
userid = "%s-%s" % (username, credname)
else:
userid = username
# Certificate request may be passed as an input but if not generate it
# here
if certReq is None:
# If no key pair was passed, generate here
if keyPair is None:
keyPair = self._createKeyPair(nBitsForKey=nBitsForKey)
certReq = self._createCertReq(username, keyPair)
if keyPair is not None:
pemKeyPair = crypto.dump_privatekey(crypto.FILETYPE_PEM, keyPair)
# Check for certificate and private key set in environment which can
# be used to authenticate with. This is an alternative to the username
# / passphrase based authentication
if sslKeyFile is None or sslCertFile is None:
sslKeyFile, sslCertFile = self.__class__.locateClientCredentials()
# Set-up SSL connection
conn = self._initConnection(certFile=sslCertFile,
keyFile=sslKeyFile,
keyFilePassphrase=sslKeyFilePassphrase)
conn.connect((self.hostname, self.port))
# send globus compatibility stuff
conn.write('0')
# send get command - ensure conversion from unicode before writing
cmd = MyProxyClient.GET_CMD % (userid, passphrase, lifetime)
conn.write(str(cmd))
# process server response
dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
respCode, errorTxt = self._deserializeResponse(dat)
if respCode:
raise MyProxyClientGetError(errorTxt)
# Send certificate request
conn.send(certReq)
# process certificates
# - 1st byte , number of certs
dat = conn.recv(1)
nCerts = ord(dat[0])
# - n certs
dat = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
# Check for error generating certificate
try:
respCode, errorTxt = self._deserializeResponse(dat)
if respCode:
raise MyProxyClientGetError(errorTxt)
except ValueError:
# If all is well, no error response will have been set so an attempt
# to parse an error code will fail
pass
# process server response
resp = conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
respCode, errorTxt = self._deserializeResponse(resp)
if respCode:
raise MyProxyClientRetrieveError(errorTxt)
# deserialize certs from received cert data
pemCerts = self._deserializeCerts(dat)
if len(pemCerts) != nCerts:
MyProxyClientRetrieveError("%d certs expected, %d received" %
(nCerts, len(pemCerts)))
if keyPair is not None:
# Return certs and private key
# - proxy or dynamically issued certificate (MyProxy CA mode)
# - private key
# - rest of cert chain if proxy cert issued
creds = [pemCerts[0], pemKeyPair]
creds.extend(pemCerts[1:])
else:
# Key generated externally - return certificate chain only
creds = pemCerts
return tuple(creds)
[docs] def getDelegation(self, *arg, **kw):
"""Retrieve proxy cert for user - same as logon"""
return self.logon(*arg, **kw)
[docs] def getTrustRoots(self,
username='',
passphrase='',
writeToCACertDir=False,
bootstrap=False):
"""Get trust roots for the given MyProxy server
:type username: basestring
:param username: username (optional)
:type passphrase: basestring
:param passphrase: pass-phrase (optional)
:type writeToCACertDir: bool
:param writeToCACertDir: if set to True, write the retrieved trust \
roots out to the directory specified by the "caCertDir" attribute
:type bootstrap: bool
:param bootstrap: If set to True, bootstrap trust roots i.e. connect \
to MyProxy server without verification of the server's SSL certificate \
against any CA certificates. Set to False, for default behaviour: \
verify server SSL certificate against CA certificates held in location \
set by the "caCertDir" attribute.
:return: trust root files as a dictionary keyed by file name with each \
item value set to the file contents
:rtype: dict
"""
if bootstrap:
log.info('Bootstrapping MyProxy server root of trust.')
# Set-up SSL connection
conn = self._initConnection(verifyPeerWithTrustRoots=(not bootstrap))
conn.connect((self.hostname, self.port))
# send globus compatibility stuff
conn.write('0')
# send get command - ensure conversion from unicode before writing
cmd = MyProxyClient.GET_TRUST_ROOTS_CMD % (username, passphrase)
conn.write(str(cmd))
# process server response chunks until all consumed
dat = ''
tries = 0
try:
for tries in range(MyProxyClient.MAX_RECV_TRIES):
dat += conn.recv(MyProxyClient.SERVER_RESP_BLK_SIZE)
except SSL.SysCallError:
# Expect this exception when response content exhausted
pass
# Precaution
if tries == MyProxyClient.MAX_RECV_TRIES:
log.warning('Maximum %d tries reached for getTrustRoots response '
'block retrieval with block size %d',
MyProxyClient.MAX_RECV_TRIES,
MyProxyClient.SERVER_RESP_BLK_SIZE)
fieldName = MyProxyClient.TRUSTED_CERTS_FIELDNAME
prefix = MyProxyClient.TRUSTED_CERTS_FILEDATA_FIELDNAME_PREFIX
respCode, errorTxt, fileData = self._deserializeResponse(dat,
fieldName,
prefix)
if respCode:
raise MyProxyClientGetTrustRootsError(errorTxt)
filesDict = dict([(k.split(prefix, 1)[1], base64.b64decode(v))
for k, v in fileData.items() if k != fieldName])
if writeToCACertDir:
# Create the CA directory path if doesn't already exist
try:
os.makedirs(self.caCertDir)
except OSError, e:
# Ignore if the path already exists
if e.errno != errno.EEXIST:
raise
for fileName, fileContents in filesDict.items():
filePath = os.path.join(self.caCertDir, fileName)
open(filePath, 'wb').write(fileContents)
return filesDict