diff --git a/README.rst b/README.rst index ea47b9d..3794113 100644 --- a/README.rst +++ b/README.rst @@ -1,46 +1,178 @@ -====== +------ PyKMIP -====== +------ PyKMIP is a Python implementation of the Key Management Interoperability Protocol (KMIP) specification, supporting version 1.1 of the KMIP standard. -The library currently provides a KMIP client, which supports the following -operations for KMIP SymmetricKey managed objects: +The KMIP standard is governed by the `Organization for the Advancement of +Structured Information Standards`_ (OASIS) and specifies a +client/server-based protocol to perform key, certificate, and secret object +management, including storage and maintenance operations. -* create -* register -* get -* destroy +The PyKMIP library currently provides a KMIP client and server supporting +the following operations for the KMIP SymmetricKey managed object: -PyKMIP also provides a software-based KMIP server, which is intended for use -in testing and demonstration environments. The server is NOT intended to be -a substitute for secured hardware-based KMIP appliances. +* Create +* Register +* Get +* Destroy -Version -======= -This distribution of PyKMIP is version 0.0.1. Future work includes adding -support for basic KMIP profiles, including the basic supporting operations. +Note that KMIP specifies profiles that tailor the standard to specific use +cases. The `KMIP Profile Support`_ section includes several profiles that +need to be developed for PyKMIP to fully support symmetric key storage and +generation capabilities. A list of operations necessary for these profiles +is included. -For more information on KMIP profiles, see the `OASIS documentation for -KMIP profiles -`_. +The PyKMIP software-based KMIP server is intended for use only in testing +and demonstration environments. Note that the PyKMIP server is **NOT** +intended to be a substitute for secured, hardware-based KMIP appliances. +The PyKMIP client should be used for operational purposes only with a +hardware-based KMIP server. The development of the PyKMIP client and server +should take place in parallel to facilitate testing of each operation as it +is developed. -Platform -======== +Platforms +========= PyKMIP has been tested and runs on Ubuntu 12.04 LTS. +.. _KMIP Profile Support: + +KMIP Profile Support +==================== +The KMIP standard includes various profiles that tailor the standard for +specific use cases (e.g., symmetric key storage with TLS 1.2). These +profiles specify conformance to certain operations and attributes. The +operations listed below are needed to support symmetric key profiles, which +are also provided below. We would appreciate help in the development of +these operations, and have listed our recommended order of development +prioritization in descending order. Since active development of these +features is already underway, please check the `code base`_ to assess the +status of operations prior to development. + +KMIP operations to add to PyKMIP: + +* Discover Versions +* List +* Locate +* Check +* Revoke +* Get Attributes +* Get Attribute List +* Add Attribute +* Modify Attribute +* Delete Attribute +* Activate +* Query + +Note that the Create, Register, Get, and Destroy operations were completed +with the initial version of PyKMIP to allow very basic KMIP symmetric key +operations. + +Server Profiles +--------------- +Server profiles that support KMIP symmetric key operations: + +* `Basic Baseline Server KMIP Profile`_ (includes TLS 1.0+) + + * Client-to-Server operations needed for this (see the `Baseline Server Clause`_) include: + + * Locate + * Check + * Get + * Get Attributes + * Get Attribute + * List + * Add Attribute + * Modify Attribute + * Delete Attribute + * Activate + * Revoke + * Destroy + * Query + * Discover Versions + +* `Symmetric Key Store and Server TLS 1.2 Authentication KMIP Profile`_ + + * Client-to-Server operations needed for this (see the `Symmetric Key Store and Server Conformance Clause`_) include all operations from the `Basic Baseline Server KMIP Profile`_ and also the Register operation. + +* `Symmetric Key Foundry and Server TLS 1.2 Authentication KMIP profile`_ + + * Client-to-Server operations needed for this (see the `Symmetric Key Foundry and Server Conformance Clause`_) include all operations from the `Basic Baseline Server KMIP Profile`_ and also the Create operation. + +Client Profiles +--------------- +Client profiles that support KMIP symmetric key operations: + +* `Basic Baseline Client KMIP Profile`_ (includes TLS 1.0+) + + * Client-to-Server operations needed for this (see the `Baseline Client Clause`_) include: + + * Locate + * Check + * Get + * Get Attributes + * Get Attribute + * List + * Add Attribute + * Modify Attribute + * Delete Attribute + * Activate + * Revoke + * Destroy + * Query + * Discover Versions + +* `Symmetric Key Store Client TLS 1.2 Authentication KMIP Profile`_ + + * Client-to-Server operations needed for this (see the `Symmetric Key Store Client Conformance Clause`_) include all operations from the `Basic Baseline Client KMIP Profile`_ and also the Register operation. + +* `Symmetric Key Foundry Client TLS 1.2 Authentication KMIP Profile`_ + + * Client-to-Server operations needed for this (see the `Symmetric Key Foundry Client Conformance Clause`_) include all operations from the `Basic Baseline Client KMIP Profile`_ and also the Create operation. + +* `Storage Client TLS 1.2 Authentication KMIP Profile`_ + + * Client-to-Server operations needed for this (see the `Storage Client Conformance Clauses`_) include all operations from the `Basic Baseline Client KMIP Profile`_, the Register operation from the `Symmetric Key Store Client TLS 1.2 Authentication KMIP Profile`_, and the Create operation from the `Symmetric Key Foundry Client TLS 1.2 Authentication KMIP Profile`_. + References ========== +The source code for PyKMIP is hosted on GitHub and the library is available +for installation from the Python Package Index (PyPI): -For more information on the KMIP specification, see the `OASIS documentation -for KMIP -`_. +* `GitHub `_ +* `PyPI `_ + +For more information on KMIP version 1.1, see the following documentation: + +* `Key Management Interoperability Protocol Specification Version 1.1`_ +* `Key Management Interoperability Protocol Profiles Version 1.1`_ +* `Key Management Interoperability Protocol Test Cases Version 1.1`_ Contributors ============ Many thanks to the developers who created PyKMIP: -Nathan Reller -Peter Hamilton -Kaitlin Farr +* `Nathan Reller `_ +* `Peter Hamilton `_ +* `Kaitlin Farr `_ + +.. _code base: https://github.com/OpenKMIP/PyKMIP +.. _Organization for the Advancement of Structured Information Standards: https://www.oasis-open.org/ +.. _Key Management Interoperability Protocol Specification Version 1.1: http://docs.oasis-open.org/kmip/spec/v1.1/os/kmip-spec-v1.1-os.html +.. _Key Management Interoperability Protocol Profiles Version 1.1: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html +.. _Key Management Interoperability Protocol Test Cases Version 1.1: http://docs.oasis-open.org/kmip/testcases/v1.1/cn01/kmip-testcases-v1.1-cn01.html +.. _Basic Baseline Server KMIP Profile: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820691 +.. _Symmetric Key Store and Server TLS 1.2 Authentication KMIP Profile: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820703 +.. _Symmetric Key Foundry and Server TLS 1.2 Authentication KMIP Profile: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820704 +.. _Basic Baseline Client KMIP Profile: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820711 +.. _Symmetric Key Store Client TLS 1.2 Authentication KMIP Profile: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820723 +.. _Symmetric Key Foundry Client TLS 1.2 Authentication KMIP Profile: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820724 +.. _Storage Client TLS 1.2 Authentication KMIP Profile: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820731 +.. _Baseline Server Clause: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820736 +.. _Symmetric Key Store and Server Conformance Clause: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820742 +.. _Symmetric Key Foundry and Server Conformance Clause: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820745 +.. _Baseline Client Clause: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820766 +.. _Symmetric Key Store Client Conformance Clause: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820772 +.. _Symmetric Key Foundry Client Conformance Clause: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820775 +.. _Storage Client Conformance Clauses: http://docs.oasis-open.org/kmip/profiles/v1.1/os/kmip-profiles-v1.1-os.html#_Toc332820793 \ No newline at end of file diff --git a/kmip/core/config_helper.py b/kmip/core/config_helper.py index 90aedbb..7fbb2e8 100644 --- a/kmip/core/config_helper.py +++ b/kmip/core/config_helper.py @@ -35,6 +35,8 @@ class ConfigHelper(object): DEFAULT_CA_CERTS = os.path.normpath(os.path.join( FILE_PATH, '../demos/certs/server.crt')) DEFAULT_SSL_VERSION = 'PROTOCOL_SSLv23' + DEFAULT_USERNAME = None + DEFAULT_PASSWORD = None def __init__(self): self.logger = logging.getLogger(__name__) diff --git a/kmip/kmipconfig.ini b/kmip/kmipconfig.ini index 1e65de9..1adc533 100644 --- a/kmip/kmipconfig.ini +++ b/kmip/kmipconfig.ini @@ -8,6 +8,8 @@ ssl_version=PROTOCOL_SSLv23 ca_certs=../demos/certs/server.crt do_handshake_on_connect=True suppress_ragged_eofs=True +username=None +password=None [server] host=127.0.0.1 diff --git a/kmip/services/kmip_client.py b/kmip/services/kmip_client.py index 51d246c..25fd500 100644 --- a/kmip/services/kmip_client.py +++ b/kmip/services/kmip_client.py @@ -22,6 +22,9 @@ from kmip.services.results import LocateResult from kmip.core import attributes as attr from kmip.core.enums import Operation as OperationEnum +from kmip.core.enums import CredentialType + +from kmip.core.factories.credentials import CredentialFactory from kmip.core import objects from kmip.core.server import KMIP @@ -55,13 +58,17 @@ class KMIPProxy(KMIP): def __init__(self, host=None, port=None, keyfile=None, certfile=None, cert_reqs=None, ssl_version=None, ca_certs=None, do_handshake_on_connect=None, - suppress_ragged_eofs=None): + suppress_ragged_eofs=None, + username=None, + password=None): super(self.__class__, self).__init__() self.logger = logging.getLogger(__name__) + self.credential_factory = CredentialFactory() self._set_variables(host, port, keyfile, certfile, cert_reqs, ssl_version, ca_certs, - do_handshake_on_connect, suppress_ragged_eofs) + do_handshake_on_connect, suppress_ragged_eofs, + username, password) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket = ssl.wrap_socket( @@ -327,9 +334,29 @@ class KMIPProxy(KMIP): uuids) return result + # TODO (peter-hamilton) Augment to handle device credentials + def _build_credential(self): + if (self.username is None) and (self.password is None): + return None + if self.username is None: + raise ValueError('cannot build credential, username is None') + if self.password is None: + raise ValueError('cannot build credential, password is None') + + credential_type = CredentialType.USERNAME_AND_PASSWORD + credential_value = {'Username': self.username, + 'Password': self.password} + credential = self.credential_factory.create_credential( + credential_type, + credential_value) + return credential + def _build_request_message(self, credential, batch_items): protocol_version = ProtocolVersion.create(1, 1) + if credential is None: + credential = self._build_credential() + authentication = None if credential is not None: authentication = Authentication(credential) @@ -352,7 +379,8 @@ class KMIPProxy(KMIP): def _set_variables(self, host, port, keyfile, certfile, cert_reqs, ssl_version, ca_certs, - do_handshake_on_connect, suppress_ragged_eofs): + do_handshake_on_connect, suppress_ragged_eofs, + username, password): conf = ConfigHelper() self.host = conf.get_valid_value( @@ -389,3 +417,9 @@ class KMIPProxy(KMIP): self.suppress_ragged_eofs = True else: self.suppress_ragged_eofs = False + + self.username = conf.get_valid_value( + username, 'client', 'username', conf.DEFAULT_USERNAME) + + self.password = conf.get_valid_value( + password, 'client', 'password', conf.DEFAULT_PASSWORD) diff --git a/kmip/tests/services/test_kmip_client.py b/kmip/tests/services/test_kmip_client.py index 4fa3ba8..a8c9b47 100644 --- a/kmip/tests/services/test_kmip_client.py +++ b/kmip/tests/services/test_kmip_client.py @@ -248,6 +248,66 @@ class TestKMIPClient(TestCase): expected, observed, 'value') self.assertEqual(expected, observed, message) + # TODO (peter-hamilton) Modify for credential type and/or add new test + def test_build_credential(self): + username = 'username' + password = 'password' + cred_type = CredentialType.USERNAME_AND_PASSWORD + self.client.username = username + self.client.password = password + + credential = self.client._build_credential() + + message = utils.build_er_error(credential.__class__, 'type', + cred_type, + credential.credential_type.enum, + 'value') + self.assertEqual(CredentialType.USERNAME_AND_PASSWORD, + credential.credential_type.enum, + message) + + message = utils.build_er_error( + credential.__class__, 'type', username, + credential.credential_value.username.value, 'value') + self.assertEqual(username, credential.credential_value.username.value, + message) + + message = utils.build_er_error( + credential.__class__, 'type', password, + credential.credential_value.password.value, 'value') + self.assertEqual(password, credential.credential_value.password.value, + message) + + def test_build_credential_no_username(self): + username = None + password = 'password' + self.client.username = username + self.client.password = password + + exception = self.assertRaises(ValueError, + self.client._build_credential) + self.assertEqual('cannot build credential, username is None', + str(exception)) + + def test_build_credential_no_password(self): + username = 'username' + password = None + self.client.username = username + self.client.password = password + + exception = self.assertRaises(ValueError, + self.client._build_credential) + self.assertEqual('cannot build credential, password is None', + str(exception)) + + def test_build_credential_no_creds(self): + self.client.username = None + self.client.password = None + + credential = self.client._build_credential() + + self.assertEqual(None, credential) + def _shutdown_server(self): if self.server.poll() is not None: return