Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/source/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ The different configuration options are defined below:
* ``password``
A string representing the password to use for KMIP requests. Optional
depending on server access policies. Leave blank if not needed.
* ``key_password``
A string representing the password to unlock the specified SSL
key, if needed. Leave blank if not needed.

The client can also be configured manually via Python. The following example
shows how to create the ``ProxyKmipClient`` in Python code, directly
Expand Down Expand Up @@ -122,7 +125,7 @@ Class Documentation
-------------------
.. py:module:: kmip.pie.client

.. py:class:: ProxyKmipClient(hostname=None, port=None, cert=None, key=None, ca=None, ssl_version=None, username=None, password=None, config='client', config_file=None, kmip_version=None)
.. py:class:: ProxyKmipClient(hostname=None, port=None, cert=None, key=None, ca=None, ssl_version=None, username=None, password=None, key_password=None, config='client', config_file=None, kmip_version=None)

A simplified KMIP client for conducting KMIP operations.

Expand Down Expand Up @@ -151,6 +154,10 @@ Class Documentation
use for operations. Optional, defaults to None.
:param string password: The password of the KMIP appliance account to
use for operations. Optional, defaults to None.
:param XXX key_password: The password to unlock the specified SSL
key, if needed. This is passed to
`SSLContext.load_cert_chain()` and can use any of the formats
accepted by that method.
:param string config: The name of a section in the PyKMIP configuration
file. Use to load a specific set of configuration settings from the
configuration file, instead of specifying them manually. Optional,
Expand Down
4 changes: 4 additions & 0 deletions kmip/pie/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def __init__(self,
ssl_version=None,
username=None,
password=None,
key_password=None,
config='client',
config_file=None,
kmip_version=None):
Expand All @@ -88,6 +89,8 @@ def __init__(self,
use for operations. Optional, defaults to None.
password (string): The password of the KMIP appliance account to
use for operations. Optional, defaults to None.
key_password (XXX): The password for the 'key', passed to
'SSLContext.load_cert_chain()'.
config (string): The name of a section in the PyKMIP configuration
file. Use to load a specific set of configuration settings from
the configuration file, instead of specifying them manually.
Expand Down Expand Up @@ -115,6 +118,7 @@ def __init__(self,
ssl_version=ssl_version,
username=username,
password=password,
key_password=key_password,
config=config,
config_file=config_file,
kmip_version=kmip_version
Expand Down
30 changes: 21 additions & 9 deletions kmip/services/kmip_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ def __init__(self, host=None, port=None, keyfile=None,
cert_reqs=None, ssl_version=None, ca_certs=None,
do_handshake_on_connect=None,
suppress_ragged_eofs=None,
username=None, password=None, timeout=30, config='client',
username=None, password=None,
key_password=None,
timeout=30, config='client',
config_file=None,
kmip_version=None):
self.logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -111,7 +113,8 @@ def __init__(self, host=None, port=None, keyfile=None,
self._set_variables(host, port, keyfile, certfile,
cert_reqs, ssl_version, ca_certs,
do_handshake_on_connect, suppress_ragged_eofs,
username, password, timeout, config_file)
username, password, key_password,
timeout, config_file)
self.batch_items = []

self.conformance_clauses = [
Expand Down Expand Up @@ -285,13 +288,18 @@ def open(self):
six.reraise(*last_error)

def _create_socket(self, sock):
self.socket = ssl.wrap_socket(
context = ssl.SSLContext(self.ssl_version)
context.verify_mode = self.cert_reqs
if self.ca_certs:
context.load_verify_locations(self.ca_certs)
if self.keyfile and not self.certfile:
raise ValueError("certfile must be specified")
if self.certfile:
context.load_cert_chain(self.certfile, self.keyfile,
password=self.key_password)
self.socket = context.wrap_socket(
sock,
keyfile=self.keyfile,
certfile=self.certfile,
cert_reqs=self.cert_reqs,
ssl_version=self.ssl_version,
ca_certs=self.ca_certs,
server_side=False,
do_handshake_on_connect=self.do_handshake_on_connect,
suppress_ragged_eofs=self.suppress_ragged_eofs)
self.socket.settimeout(self.timeout)
Expand Down Expand Up @@ -1738,7 +1746,8 @@ def _send_and_receive_message(self, request):
def _set_variables(self, host, port, keyfile, certfile,
cert_reqs, ssl_version, ca_certs,
do_handshake_on_connect, suppress_ragged_eofs,
username, password, timeout, config_file):
username, password, key_password,
timeout, config_file):
conf = ConfigHelper(config_file)

# TODO: set this to a host list
Expand Down Expand Up @@ -1787,6 +1796,9 @@ def _set_variables(self, host, port, keyfile, certfile,
self.password = conf.get_valid_value(
password, self.config, 'password', conf.DEFAULT_PASSWORD)

self.key_password = conf.get_valid_value(
key_password, self.config, 'key_password', None)

self.timeout = int(conf.get_valid_value(
timeout, self.config, 'timeout', conf.DEFAULT_TIMEOUT))
if self.timeout < 0:
Expand Down
25 changes: 17 additions & 8 deletions kmip/services/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,17 +287,26 @@ def interrupt_handler(trigger, frame):
for cipher in auth_suite_ciphers:
self._logger.debug(cipher)

self._socket = ssl.wrap_socket(
cafile = self.config.settings.get('ca_path')
context = ssl.SSLContext(self.auth_suite.protocol)
context.verify_mode = ssl.CERT_REQUIRED
if self.auth_suite.ciphers:
context.set_ciphers(self.auth_suite.ciphers)
if cafile:
context.load_verify_locations(cafile)
certfile = self.config.settings.get('certificate_path')

if certfile:
keyfile = self.config.settings.get('key_path')
context.load_cert_chain(certfile, keyfile=keyfile)
else:
raise ValueError("certfile must be specified for server-side operations")

self._socket = context.wrap_socket(
self._socket,
keyfile=self.config.settings.get('key_path'),
certfile=self.config.settings.get('certificate_path'),
server_side=True,
cert_reqs=ssl.CERT_REQUIRED,
ssl_version=self.auth_suite.protocol,
ca_certs=self.config.settings.get('ca_path'),
do_handshake_on_connect=False,
suppress_ragged_eofs=True,
ciphers=self.auth_suite.ciphers
suppress_ragged_eofs=True
)

try:
Expand Down
6 changes: 3 additions & 3 deletions kmip/tests/unit/services/server/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ def test_start(self,
# Test that in ideal cases no errors are generated and the right
# log messages are.
with mock.patch('socket.socket') as socket_mock:
with mock.patch('ssl.wrap_socket') as ssl_mock:
with mock.patch('ssl.SSLContext') as ssl_mock:
socket_mock.return_value = a_mock
ssl_mock.return_value = b_mock
ssl_mock.return_value.wrap_socket.return_value = b_mock

manager_mock.assert_not_called()
monitor_mock.assert_not_called()
Expand Down Expand Up @@ -271,7 +271,7 @@ def test_start(self,

# Test that a NetworkingError is generated if the socket bind fails.
with mock.patch('socket.socket') as socket_mock:
with mock.patch('ssl.wrap_socket') as ssl_mock:
with mock.patch('ssl.SSLContext.wrap_socket') as ssl_mock:
socket_mock.return_value = a_mock
ssl_mock.return_value = b_mock

Expand Down
1 change: 1 addition & 0 deletions kmip/tests/unit/services/test_kmip_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,7 @@ def test_host_list_import_string(self):
suppress_ragged_eofs=None,
username=None,
password=None,
key_password=None,
timeout=None,
config_file=None
)
Expand Down