diff -Nru python-pykmip-0.9.1/CHANGELOG.rst python-pykmip-0.10.0/CHANGELOG.rst --- python-pykmip-0.9.1/CHANGELOG.rst 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/CHANGELOG.rst 2020-02-25 16:05:27.000000000 +0000 @@ -4,10 +4,27 @@ .. _v0.10: -0.10 - `master`_ -~~~~~~~~~~~~~~~~ - -.. note:: This version is not yet released and is under active development. +0.10 - February 25, 2020 +~~~~~~~~~~~~~~~~~~~~~~~~ +* Add server debug logging for message encodings +* Add server Locate filtering for all currently supported attributes +* Add server Locate filtering using offset and maximum item constraints +* Add server cryptography engine support for AES GCM mode +* Add server support for the SplitKey object +* Add client/server support for the ApplicationSpecificInformation attribute +* Add client/server support for the ObjectGroup and Sensitive attributes +* Add client/server support for the DeleteAttribute operation +* Add client/server support for the SetAttribute operation +* Add client/server support for the ModifyAttribute operation +* Add a variety of unit and integration tests to cover all new functionality +* Add new ProxyKmipClient demo scripts to show how to use the new operations +* Add pending deprecation warnings for Python 2.7 and 3.4 due to their EOL +* Update server Locate filtering to return results sorted by creation date +* Update encoding support for SplitKey objects +* Update the Travis CI configuration to better support default Python versions +* Update library and testing dependencies to maintain Python 3.4 support +* Update the library documentation to reflect new features and security details +* Fix a bug with how key pair names are handled by the client for KMIP 2.0 .. _v0.9.1: @@ -262,4 +279,3 @@ * Add client/server support for Create, Get, Register, and Destroy operations * Add unit test suite -.. _`master`: https://github.com/openkmip/pykmip/ diff -Nru python-pykmip-0.9.1/debian/changelog python-pykmip-0.10.0/debian/changelog --- python-pykmip-0.9.1/debian/changelog 2019-10-21 08:15:41.000000000 +0000 +++ python-pykmip-0.10.0/debian/changelog 2020-05-08 09:28:10.000000000 +0000 @@ -1,3 +1,24 @@ +python-pykmip (0.10.0-3) unstable; urgency=medium + + * Uploading to unstable. + + -- Thomas Goirand Fri, 08 May 2020 11:28:10 +0200 + +python-pykmip (0.10.0-2) experimental; urgency=medium + + * Add fix-exception-using-py2-format.patch. + + -- Thomas Goirand Fri, 24 Apr 2020 22:31:04 +0200 + +python-pykmip (0.10.0-1) experimental; urgency=medium + + * New upstream release. + * Add python3-yaml as (build-)depends. + * Standards-Version: 4.5.0. + * debhelper-compat (= 11). + + -- Thomas Goirand Tue, 07 Apr 2020 12:48:41 +0200 + python-pykmip (0.9.1-2) unstable; urgency=medium * Uploading to unstable. diff -Nru python-pykmip-0.9.1/debian/control python-pykmip-0.10.0/debian/control --- python-pykmip-0.9.1/debian/control 2019-10-21 08:15:41.000000000 +0000 +++ python-pykmip-0.10.0/debian/control 2020-05-08 09:28:10.000000000 +0000 @@ -5,7 +5,7 @@ Uploaders: Thomas Goirand , Build-Depends: - debhelper-compat (= 10), + debhelper-compat (= 11), dh-python, openstack-pkg-tools, python3-all, @@ -22,9 +22,10 @@ python3-testresources, python3-testscenarios, python3-testtools, + python3-yaml, subunit, testrepository, -Standards-Version: 4.1.3 +Standards-Version: 4.5.0 Vcs-Browser: https://salsa.debian.org/openstack-team/python/python-pykmip Vcs-Git: https://salsa.debian.org/openstack-team/python/python-pykmip.git Homepage: https://github.com/OpenKMIP/PyKMIP @@ -37,6 +38,7 @@ python3-cryptography, python3-six, python3-sqlalchemy, + python3-yaml, ${misc:Depends}, ${python3:Depends}, Description: KMIP v1.1 library - Python 3.x diff -Nru python-pykmip-0.9.1/debian/patches/fix-exception-using-py2-format.patch python-pykmip-0.10.0/debian/patches/fix-exception-using-py2-format.patch --- python-pykmip-0.9.1/debian/patches/fix-exception-using-py2-format.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/debian/patches/fix-exception-using-py2-format.patch 2020-05-08 09:28:10.000000000 +0000 @@ -0,0 +1,16 @@ +Description: Fix exception using py2 format +Author: Thomas Goirand +Forwarded: no +Last-Update: 2020-04-24 + +--- python-pykmip-0.10.0.orig/kmip/demos/pie/locate.py ++++ python-pykmip-0.10.0/kmip/demos/pie/locate.py +@@ -63,7 +63,7 @@ if __name__ == '__main__': + for initial_date in initial_dates: + try: + t = time.strptime(initial_date) +- except ValueError, TypeError: ++ except ValueError as TypeError: + logger.error( + "Invalid initial date provided: {}".format(initial_date) + ) diff -Nru python-pykmip-0.9.1/debian/patches/series python-pykmip-0.10.0/debian/patches/series --- python-pykmip-0.9.1/debian/patches/series 2019-10-21 08:15:41.000000000 +0000 +++ python-pykmip-0.10.0/debian/patches/series 2020-05-08 09:28:10.000000000 +0000 @@ -1 +1,2 @@ kill-broken-test.patch +fix-exception-using-py2-format.patch diff -Nru python-pykmip-0.9.1/docs/source/client.rst python-pykmip-0.10.0/docs/source/client.rst --- python-pykmip-0.9.1/docs/source/client.rst 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/docs/source/client.rst 2020-02-25 16:05:27.000000000 +0000 @@ -93,7 +93,7 @@ .. code-block:: python >>> import ssl - >>> from kmip.pie.client import ProxyKmipClient + >>> from kmip.pie.client import ProxyKmipClient, enums >>> client = ProxyKmipClient( ... hostname='127.0.0.1', ... port=5696, @@ -102,7 +102,7 @@ ... ca='/path/to/ca/certificate/file', ... ssl_version=ssl.PROTOCOL_SSLv23, ... username='example_username', - ... password='example_password' + ... password='example_password', ... config='client', ... config_file='/etc/pykmip/pykmip.conf', ... kmip_version=enums.KMIPVersion.KMIP_1_2 @@ -184,6 +184,73 @@ :raises Exception: This is raised if an error occurs while trying to close the connection. + .. py:method:: activate(uid=None) + + Activate a managed object stored by a KMIP appliance. + + :param string uid: The unique ID of the managed object to activate. + Optional, defaults to None. + + :return: None + + :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if + the client connection is unusable. + :raises kmip.pie.exceptions.KmipOperationFailure: This is raised if the + operation result is a failure. + :raises TypeError: This is raised if the input argument is invalid. + + Activating a symmetric key would look like this: + + .. code-block:: python + + >>> from kmip.pie import objects + >>> from kmip.pie import client + >>> from kmip import enums + >>> c = client.ProxyKmipClient() + >>> symmetric_key = objects.SymmetricKey( + ... enums.CryptographicAlgorithm.AES, + ... 128, + ... ( + ... b'\x00\x01\x02\x03\x04\x05\x06\x07' + ... b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + ... ) + ... ) + >>> with c: + ... key_id = c.register(symmetric_key) + ... c.activate(key_id) + + .. py:method:: check(uid=None, usage_limits_count=None, cryptographic_usage_mask=None, lease_time=None) + + Check the constraints for a managed object. + + :param string uid: The unique ID of the managed object to check. + :param int usage_limits_count: The number of items that can be secured + with the specified managed object. + :param list cryptographic_usage_mask: A list of :class:`kmip.core.enums.CryptographicUsageMask` + enumerations specifying the operations allowed for the specified + managed object. + :param int least_time: The number of seconds that can be leased for the + specified managed object. + + :return: The string ID of the managed object that was checked. + + :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if + the client connection is unusable. + :raises kmip.pie.exceptions.KmipOperationFailure: This is raised if the + operation result is a failure. + :raises TypeError: This is raised if the input arguments are invalid. + + .. code-block:: python + + >>> from kmip.pie import client + >>> c = client.ProxyKmipClient() + >>> with c: + ... c.check( + ... uid="1", + ... usage_limits_count=50 + ... ) + '1' + .. py:method:: create(algorithm, length, operation_policy_name=None, name=None, cryptographic_usage_mask=None) Create a symmetric key on a KMIP appliance. @@ -286,14 +353,22 @@ ... ) ('450', '451') - .. py:method:: register(managed_object) + .. py:method:: decrypt(data, uid=None, cryptographic_parameters=None, iv_counter_nonce=None) - Register a managed object with a KMIP appliance. + Decrypt data using the specified decryption key and parameters. - :param managed_object: A :class:`kmip.pie.objects.ManagedObject` - instance to register with the server. + :param bytes data: The bytes to decrypt. Required. + :param string uid: The unique ID of the decryption key to use. + Optional, defaults to None. + :param dict cryptographic_parameters: A dictionary containing various + cryptographic settings to be used for the decryption. Optional, + defaults to None. See :term:`cryptographic_parameters` for more + information. + :param bytes iv_counter_nonce: The bytes to use for the IV/counter/ + nonce, if needed by the decryption algorithm and/or cipher mode. + Optional, defaults to None. - :return: The string uid of the newly registered managed object. + :return: The decrypted data bytes. :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if the client connection is unusable. @@ -301,7 +376,7 @@ operation result is a failure. :raises TypeError: This is raised if the input argument is invalid. - Registering an existing 128-bit AES symmetric key would look like this: + Decrypting cipher text with a symmetric key would look like this: .. code-block:: python @@ -309,18 +384,96 @@ >>> from kmip.pie import client >>> from kmip import enums >>> c = client.ProxyKmipClient() - >>> symmetric_key = objects.SymmetricKey( - ... enums.CryptographicAlgorithm.AES, - ... 128, - ... ( - ... b'\x00\x01\x02\x03\x04\x05\x06\x07' - ... b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' - ... ) - ... ) >>> with c: - ... c.register(symmetric_key) + ... key_id = c.create( + ... enums.CryptographicAlgorithm.AES, + ... 256, + ... cryptographic_usage_mask=[ + ... enums.CryptographicUsageMask.ENCRYPT, + ... enums.CryptographicUsageMask.DECRYPT + ... ] + ... ) + ... c.activate(key_id) + ... c.decrypt( + ... ( + ... b' \xb6:s0\x16\xea\t\x1b\x16\xed\xb2\x04-\xd6' + ... b'\xb6\\\xf3xJ\xfe\xa7[\x1eJ\x08I\xae\x14\xd2' + ... b\xdb\xe2' + ... ), + ... uid=key_id, + ... cryptographic_parameters={ + ... 'cryptographic_algorithm': + ... enums.CryptographicAlgorithm.AES, + ... 'block_cipher_mode': enums.BlockCipherMode.CBC, + ... 'padding_method': enums.PaddingMethod.PKCS5 + ... }, + ... iv_counter_nonce=( + ... b'\x85\x1e\x87\x64\x77\x6e\x67\x96' + ... b'\xaa\xb7\x22\xdb\xb6\x44\xac\xe8' + ... ) + ... ) ... - '452' + b'This is a secret message.' + + .. py:method:: delete_attribute(unique_identifier=None, **kwargs) + + Delete an attribute from a managed object. + + :param string unique_identifier: The unique ID of the managed object + from which to delete the specified attribute. + :param `**kwargs`: A placeholder for attribute values used to identify + the attribute to delete. See the examples below for more + information. + + :return: The string ID of the managed object from which the + attribute was deleted. + :return: A :class:`kmip.core.primitives.Struct` object + representing the deleted attribute. Only returned for KMIP + 1.0 - 1.4 messages. + + For KMIP 1.0 - 1.4, the supported `kwargs` values are: + + * `attribute_name` (string): The name of the attribute to delete. Required. + * `attribute_index` (int): The index of the attribute to delete. Defaults to zero. + + .. code-block:: python + + >>> from kmip.pie import client + >>> c = client.ProxyKmipClient() + >>> with c: + ... c.delete_attribute( + ... unique_identifier="1", + ... attribute_name="Name", + ... attribute_index=0 + ... ) + ('1', Attribute(...)) + + For KMIP 2.0+, the supported `kwargs` values are: + + * `current_attribute` (struct): A :class:`kmip.core.objects.CurrentAttribute` object containing the attribute to delete. Required if the attribute reference is not specified. + * `attribute_reference` (struct): A :class:`kmip.core.objects.AttributeReference` object containing the name of the attribute to delete. Required if the current attribute is not specified. + + .. code-block:: python + + >>> from kmip.pie import client + >>> from kmip import enums + >>> from kmip.core import objects, primitives + >>> c = client.ProxyKmipClient() + >>> with c: + ... c.delete_attribute( + ... unique_identifier="1", + ... current_attribute=objects.CurrentAttribute( + ... attribute=primitives.TextString( + ... value="Object Group 1", + ... tag=enums.Tags.OBJECT_GROUP + ... ), + ... ), + ... attribute_reference=objects.AttributeReference( + ... vendor_identification="Vendor 1", + ... attribute_name="Object Group" + ... ) + ... ) + '1' .. py:method:: derive_key(object_type, unique_identifiers, derivation_method, derivation_parameters, **kwargs) @@ -529,9 +682,99 @@ ... '460' - .. py:method:: locate(maximum_items=None, storage_status_mask=None, object_group_member=None, attributes=None) + .. py:method:: destroy(uid=None) + + Destroy a managed object stored by a KMIP appliance. - Documentation coming soon. + :param string uid: The unique ID of the managed object to destroy. + + :return: None + + :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if + the client connection is unusable. + :raises kmip.pie.exceptions.KmipOperationFailure: This is raised if the + operation result is a failure. + :raises TypeError: This is raised if the input argument is invalid. + + Destroying a symmetric key would look like this: + + .. code-block:: python + + >>> from kmip.pie import objects + >>> from kmip.pie import client + >>> from kmip import enums + >>> c = client.ProxyKmipClient() + >>> symmetric_key = objects.SymmetricKey( + ... enums.CryptographicAlgorithm.AES, + ... 128, + ... ( + ... b'\x00\x01\x02\x03\x04\x05\x06\x07' + ... b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + ... ) + ... ) + >>> with c: + ... key_id = c.register(symmetric_key) + ... c.destroy(key_id) + + .. py:method:: encrypt(data, uid=None, cryptographic_parameters=None, iv_counter_nonce=None) + + Encrypt data using the specified encryption key and parameters. + + :param bytes data: The bytes to encrypt. Required. + :param string uid: The unique ID of the encryption key to use. + Optional, defaults to None. + :param dict cryptographic_parameters: A dictionary containing various + cryptographic settings to be used for the encryption. Optional, + defaults to None. See :term:`cryptographic_parameters` for more + information. + :param bytes iv_counter_nonce: The bytes to use for the IV/counter/ + nonce, if needed by the encryption algorithm and/or cipher mode. + Optional, defaults to None. + + :return: The encrypted data bytes. + :return: The IV/counter/nonce bytes used with the encryption algorithm, + only if it was autogenerated by the server. + + :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if + the client connection is unusable. + :raises kmip.pie.exceptions.KmipOperationFailure: This is raised if the + operation result is a failure. + :raises TypeError: This is raised if the input argument is invalid. + + Encrypting plain text with a symmetric key would look like this: + + .. code-block:: python + + >>> from kmip.pie import objects + >>> from kmip.pie import client + >>> from kmip import enums + >>> c = client.ProxyKmipClient() + >>> with c: + ... key_id = c.create( + ... enums.CryptographicAlgorithm.AES, + ... 256, + ... cryptographic_usage_mask=[ + ... enums.CryptographicUsageMask.ENCRYPT, + ... enums.CryptographicUsageMask.DECRYPT + ... ] + ... ) + ... c.activate(key_id) + ... c.encrypt( + ... b'This is a secret message.', + ... uid=key_id, + ... cryptographic_parameters={ + ... 'cryptographic_algorithm': + ... enums.CryptographicAlgorithm.AES, + ... 'block_cipher_mode': enums.BlockCipherMode.CBC, + ... 'padding_method': enums.PaddingMethod.PKCS5 + ... }, + ... iv_counter_nonce=( + ... b'\x85\x1e\x87\x64\x77\x6e\x67\x96' + ... b'\xaa\xb7\x22\xdb\xb6\x44\xac\xe8' + ... ) + ... ) + ... + (b'...', None) .. py:method:: get(uid=None, key_wrapping_specification=None) @@ -742,14 +985,24 @@ 'Unique Identifier' ] - .. py:method:: activate(uid=None) + .. py:method:: locate(maximum_items=None, storage_status_mask=None, object_group_member=None, offset_items=None, attributes=None) - Activate a managed object stored by a KMIP appliance. + Search for managed objects with specific matching attributes. - :param string uid: The unique ID of the managed object to activate. - Optional, defaults to None. + :param int maximum_items: The maximum number of results that should be + returned. + :param int storage_status_mask: A bit-mask indicating whether online or + archived objects should be included in the search. See + :term:`storage_status` for more information. + :param enum object_group_member: A :class:`kmip.core.enums.ObjectGroupMember` + enumeration indicating the object group member type. See + :term:`object_group_member` for more information. + :param int offset_items: The number of results that should be skipped + before results are returned. + :param list attributes: A list of :class:`kmip.core.objects.Attribute` + objects representing an attribute filter for the search. - :return: None + :return: A list of string IDs of all matching objects, per the operation parameters. :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if the client connection is unusable. @@ -757,46 +1010,38 @@ operation result is a failure. :raises TypeError: This is raised if the input argument is invalid. - Activating a symmetric key would look like this: - .. code-block:: python - >>> from kmip.pie import objects >>> from kmip.pie import client - >>> from kmip import enums + >>> from kmip.core import enums + >>> from kmip.core.factories import attributes + >>> f = attributes.AttributeFactory() >>> c = client.ProxyKmipClient() - >>> symmetric_key = objects.SymmetricKey( - ... enums.CryptographicAlgorithm.AES, - ... 128, - ... ( - ... b'\x00\x01\x02\x03\x04\x05\x06\x07' - ... b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' - ... ) - ... ) >>> with c: - ... key_id = c.register(symmetric_key) - ... c.activate(key_id) - - .. py:method:: revoke(revocation_reason, uid=None, revocation_message=None, compromise_occurrence_date=None) + ... c.locate( + ... attributes=[ + ... f.create_attribute( + ... enums.AttributeType.OBJECT_TYPE, + ... enums.ObjectType.SYMMETRIC_KEY + ... ) + ... ] + ... ) + ['1', '2', '3'] - Revoke a managed object stored by a KMIP appliance. + .. py:method:: mac(data, uid=None, algorithm=None) - Activated objects must be revoked before they can be destroyed. + Get the message authentication code for a piece of data. - :param revocation_reason: A - :class:`kmip.core.enums.RevocationReasonCode` enumeration - indicating the revocation reason. See - :term:`revocation_reason_code` for more information. - :param string uid: The unique ID of the managed object to revoke. - Optional, defaults to None. - :param string revocation_message: A message regarding the revocation. - Optional, defaults to None. - :param int compromise_occurrence_date: An integer, the number of - seconds since the epoch, which will be converted to the Datetime - when the managed object was first believed to be compromised. - Optional, defaults to None. + :param string data: The data to be MACed. + :param string uid: The unique ID of the managed object that is the key + to be used in the MAC operation. + :param enum algorithm: A :class:`kmip.core.enums.CryptographicAlgorithm` + enumeration specifying the algorithm to use in the MAC operation. + See :term:`cryptographic_algorithm` for more information. - :return: None + :return: The string ID of the managed object that is the key used in + the MAC operation. + :return: The bytestring representing the MAC of the data. :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if the client connection is unusable. @@ -804,37 +1049,94 @@ operation result is a failure. :raises TypeError: This is raised if the input argument is invalid. - Revoking an activated symmetric key would look like this: + .. code-block:: python + + >>> from kmip.pie import client + >>> c = client.ProxyKmipClient() + >>> with c: + ... c.mac( + ... b'\x01\x02\x03\x04', + ... uid="5", + ... algorithm=enums.CryptographicAlgorithm.HMAC_SHA512 + ... ) + ('5', b'...') + + .. py:method:: modify_attribute(unique_identifier=None, **kwargs) + + Modify an attribute on a managed object. + + :param string unique_identifier: The unique ID of the managed object + on which to set the specified attribute. + :param `**kwargs`: A placeholder for attribute values used to identify + the attribute to set. See the example below for more + information. + + :return: The string ID of the managed object on which the attribute + was set. + :return: A :class:`kmip.core.primitives.Struct` object representing + the modified attribute. Only returned for KMIP 1.0 - 1.4 messages. + + For KMIP 1.0 - 1.4, the supported `kwargs` values are: + + * `attribute` (struct): A :class:`kmip.core.objects.Attribute` object + containing the details required to identify and modify an existing + attribute on the specified managed object. Required. .. code-block:: python - >>> from kmip.pie import objects >>> from kmip.pie import client - >>> from kmip import enums + >>> from kmip.core import enums + >>> from kmip.core.factories import attributes + >>> f = attributes.AttributeFactory() >>> c = client.ProxyKmipClient() - >>> symmetric_key = objects.SymmetricKey( - ... enums.CryptographicAlgorithm.AES, - ... 128, - ... ( - ... b'\x00\x01\x02\x03\x04\x05\x06\x07' - ... b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + >>> with c: + ... c.modify_attribute( + ... unique_identifier="1", + ... attribute=f.create_attribute( + ... enums.AttributeType.NAME, + ... "New Name", + ... index=0 + ... ) ... ) - ... ) + ('1', Attribute(...)) + + For KMIP 2.0+, the supported `kwargs` values are: + + * `current_attribute` (struct): A :class:`kmip.core.objects.CurrentAttribute` object containing the existing attribute to modify. Required if the attribute is multivalued. + * `new_attribute` (struct): A :class:`kmip.core.objects.NewAttribute` object containing the new attribute. Required. + + .. code-block:: python + + >>> from kmip.pie import client + >>> from kmip import enums + >>> from kmip.core import objects, primitives + >>> c = client.ProxyKmipClient() >>> with c: - ... key_id = c.register(symmetric_key) - ... c.activate(key_id) - ... c.revoke( - ... enums.RevocationReasonCode.CESSATION_OF_OPERATION, - ... key_id + ... c.modify_attribute( + ... unique_identifier="1", + ... current_attribute=objects.CurrentAttribute( + ... attribute=primitives.TextString( + ... value="Old Object Group", + ... tag=enums.Tags.OBJECT_GROUP + ... ), + ... ), + ... new_attribute=objects.NewAttribute( + ... attribute=primitives.TextString( + ... value="New Object Group", + ... tag=enums.Tags.OBJECT_GROUP + ... ) + ... ) ... ) + '1' - .. py:method:: destroy(uid=None) + .. py:method:: register(managed_object) - Destroy a managed object stored by a KMIP appliance. + Register a managed object with a KMIP appliance. - :param string uid: The unique ID of the managed object to destroy. + :param managed_object: A :class:`kmip.pie.objects.ManagedObject` + instance to register with the server. - :return: None + :return: The string uid of the newly registered managed object. :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if the client connection is unusable. @@ -842,7 +1144,7 @@ operation result is a failure. :raises TypeError: This is raised if the input argument is invalid. - Destroying a symmetric key would look like this: + Registering an existing 128-bit AES symmetric key would look like this: .. code-block:: python @@ -859,27 +1161,22 @@ ... ) ... ) >>> with c: - ... key_id = c.register(symmetric_key) - ... c.destroy(key_id) + ... c.register(symmetric_key) + ... + '452' - .. py:method:: encrypt(data, uid=None, cryptographic_parameters=None, iv_counter_nonce=None) + .. py:method:: rekey(uid=None, offset=None, **kwargs) - Encrypt data using the specified encryption key and parameters. + Rekey an existing key. - :param bytes data: The bytes to encrypt. Required. - :param string uid: The unique ID of the encryption key to use. - Optional, defaults to None. - :param dict cryptographic_parameters: A dictionary containing various - cryptographic settings to be used for the encryption. Optional, - defaults to None. See :term:`cryptographic_parameters` for more - information. - :param bytes iv_counter_nonce: The bytes to use for the IV/counter/ - nonce, if needed by the encryption algorithm and/or cipher mode. - Optional, defaults to None. + :param string uid: The unique ID of the managed object that is the key + to rekey. + :param int offset: The time delta, in seconds, between the new key's + initialization date and activation date. + :param `**kwargs`: A placeholder for object attributes that + should be set on the newly rekeyed key. - :return: The encrypted data bytes. - :return: The IV/counter/nonce bytes used with the encryption algorithm, - only if it was autogenerated by the server. + :return: The string ID of the newly rekeyed key. :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if the client connection is unusable. @@ -887,57 +1184,44 @@ operation result is a failure. :raises TypeError: This is raised if the input argument is invalid. - Encrypting plain text with a symmetric key would look like this: + The current set of supported `kwargs` values are: + + * `activation_date` (int): The new key's activation date, in seconds since the epoch. + * `process_start_date` (int): The new key's process start date, in seconds since the epoch. + * `protect_stop_date` (int): The new key's protect stop date, in seconds since the epoch. + * `deactivation_date` (int): The new key's deactivation date, in seconds since the epoch. .. code-block:: python - >>> from kmip.pie import objects >>> from kmip.pie import client - >>> from kmip import enums >>> c = client.ProxyKmipClient() >>> with c: - ... key_id = c.create( - ... enums.CryptographicAlgorithm.AES, - ... 256, - ... cryptographic_usage_mask=[ - ... enums.CryptographicUsageMask.ENCRYPT, - ... enums.CryptographicUsageMask.DECRYPT - ... ] + ... c.rekey( + ... uid="1", + ... offset=60 ... ) - ... c.activate(key_id) - ... c.encrypt( - ... b'This is a secret message.', - ... uid=key_id, - ... cryptographic_parameters={ - ... 'cryptographic_algorithm': - ... enums.CryptographicAlgorithm.AES, - ... 'block_cipher_mode': enums.BlockCipherMode.CBC, - ... 'padding_method': enums.PaddingMethod.PKCS5 - ... }, - ... iv_counter_nonce=( - ... b'\x85\x1e\x87\x64\x77\x6e\x67\x96' - ... b'\xaa\xb7\x22\xdb\xb6\x44\xac\xe8' - ... ) - ... ) - ... - (b'...', None) + "2" - .. py:method:: decrypt(data, uid=None, cryptographic_parameters=None, iv_counter_nonce=None) + .. py:method:: revoke(revocation_reason, uid=None, revocation_message=None, compromise_occurrence_date=None) - Decrypt data using the specified decryption key and parameters. + Revoke a managed object stored by a KMIP appliance. - :param bytes data: The bytes to decrypt. Required. - :param string uid: The unique ID of the decryption key to use. + Activated objects must be revoked before they can be destroyed. + + :param revocation_reason: A + :class:`kmip.core.enums.RevocationReasonCode` enumeration + indicating the revocation reason. See + :term:`revocation_reason_code` for more information. + :param string uid: The unique ID of the managed object to revoke. Optional, defaults to None. - :param dict cryptographic_parameters: A dictionary containing various - cryptographic settings to be used for the decryption. Optional, - defaults to None. See :term:`cryptographic_parameters` for more - information. - :param bytes iv_counter_nonce: The bytes to use for the IV/counter/ - nonce, if needed by the decryption algorithm and/or cipher mode. + :param string revocation_message: A message regarding the revocation. + Optional, defaults to None. + :param int compromise_occurrence_date: An integer, the number of + seconds since the epoch, which will be converted to the Datetime + when the managed object was first believed to be compromised. Optional, defaults to None. - :return: The decrypted data bytes. + :return: None :raises kmip.pie.exceptions.ClientConnectionNotOpen: This is raised if the client connection is unusable. @@ -945,7 +1229,7 @@ operation result is a failure. :raises TypeError: This is raised if the input argument is invalid. - Decrypting cipher text with a symmetric key would look like this: + Revoking an activated symmetric key would look like this: .. code-block:: python @@ -953,36 +1237,52 @@ >>> from kmip.pie import client >>> from kmip import enums >>> c = client.ProxyKmipClient() - >>> with c: - ... key_id = c.create( - ... enums.CryptographicAlgorithm.AES, - ... 256, - ... cryptographic_usage_mask=[ - ... enums.CryptographicUsageMask.ENCRYPT, - ... enums.CryptographicUsageMask.DECRYPT - ... ] + >>> symmetric_key = objects.SymmetricKey( + ... enums.CryptographicAlgorithm.AES, + ... 128, + ... ( + ... b'\x00\x01\x02\x03\x04\x05\x06\x07' + ... b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' ... ) + ... ) + >>> with c: + ... key_id = c.register(symmetric_key) ... c.activate(key_id) - ... c.decrypt( - ... ( - ... b' \xb6:s0\x16\xea\t\x1b\x16\xed\xb2\x04-\xd6' - ... b'\xb6\\\xf3xJ\xfe\xa7[\x1eJ\x08I\xae\x14\xd2' - ... b\xdb\xe2' - ... ), - ... uid=key_id, - ... cryptographic_parameters={ - ... 'cryptographic_algorithm': - ... enums.CryptographicAlgorithm.AES, - ... 'block_cipher_mode': enums.BlockCipherMode.CBC, - ... 'padding_method': enums.PaddingMethod.PKCS5 - ... }, - ... iv_counter_nonce=( - ... b'\x85\x1e\x87\x64\x77\x6e\x67\x96' - ... b'\xaa\xb7\x22\xdb\xb6\x44\xac\xe8' - ... ) + ... c.revoke( + ... enums.RevocationReasonCode.CESSATION_OF_OPERATION, + ... key_id ... ) - ... - b'This is a secret message.' + + .. py:method:: set_attribute(unique_identifier=None, **kwargs) + + Set an attribute on a managed object. + + :param string unique_identifier: The unique ID of the managed object + on which to set the specified attribute. + :param `**kwargs`: A placeholder for attribute values used to identify + the attribute to set. See the example below for more + information. + + :return: The string ID of the managed object on which the attribute + was set. + + This operation is supported by KMIP 2.0+ only. The supported `kwargs` + values are: + + * `attribute_name` (string): The name of the attribute to set. Required. + * `attribute_value` (various): The value of the attribute to set. Required. + + .. code-block:: python + + >>> from kmip.pie import client + >>> c = client.ProxyKmipClient() + >>> with c: + ... c.set_attribute( + ... unique_identifier="1", + ... attribute_name="Sensitive", + ... attribute_value=True + ... ) + '1' .. py:method:: sign(data, uid=None, cryptographic_parameters=None) @@ -1098,9 +1398,4 @@ ... - .. py:method:: mac(data, uid=None, algorithm=None) - - Documentation coming soon. - - .. _`ssl`: https://docs.python.org/dev/library/ssl.html#socket-creation diff -Nru python-pykmip-0.9.1/docs/source/community.rst python-pykmip-0.10.0/docs/source/community.rst --- python-pykmip-0.9.1/docs/source/community.rst 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/docs/source/community.rst 2020-02-25 16:05:27.000000000 +0000 @@ -4,8 +4,6 @@ * `Source code`_ * `Issue tracker`_ -* IRC: ``#pykmip`` on ``irc.freenode.net`` -* Twitter: ``@pykmip`` .. _`Source code`: https://github.com/openkmip/pykmip .. _`Issue tracker`: https://github.com/openkmip/pykmip/issues diff -Nru python-pykmip-0.9.1/docs/source/index.rst python-pykmip-0.10.0/docs/source/index.rst --- python-pykmip-0.9.1/docs/source/index.rst 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/docs/source/index.rst 2020-02-25 16:05:27.000000000 +0000 @@ -40,6 +40,7 @@ changelog faq development + security client server community diff -Nru python-pykmip-0.9.1/docs/source/installation.rst python-pykmip-0.10.0/docs/source/installation.rst --- python-pykmip-0.9.1/docs/source/installation.rst 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/docs/source/installation.rst 2020-02-25 16:05:27.000000000 +0000 @@ -8,7 +8,7 @@ Supported platforms ------------------- -PyKMIP is tested on Python 2.7, 3.4, 3.5, and 3.6 on the following +PyKMIP is tested on Python 2.7, 3.4, 3.5, 3.6, and 3.7 on the following operating systems: * Ubuntu 12.04, 14.04, and 16.04 diff -Nru python-pykmip-0.9.1/docs/source/security.rst python-pykmip-0.10.0/docs/source/security.rst --- python-pykmip-0.9.1/docs/source/security.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/docs/source/security.rst 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,40 @@ +Security +======== +The PyKMIP development team takes security seriously and will respond promptly +to any reported security issue. Use the information provided here to inform +your security posture. + +Reporting a Security Issue +-------------------------- +If you discover a new PyKMIP security issue, please follow responsible +disclosure best practices and contact the project maintainers in private over +email to discuss the issue before filing a public GitHub issue. When reporting +a security issue, please include as much detail as possible. This includes: + +* a high-level description of the issue +* information on how to cause or reproduce the issue +* any details on specific portions of the project code base related to the issue + +Once you have provided this information, you should receive an acknowledgement. +Depending upon the severity of the issue, the project maintainers will respond +to collect additional information and work with you to address the security +issue. If applicable, a new library subrelease will be produced across all +actively supported releases to address and fix the issue. + +If the developerment team decides the issue does not warrant the sensitivity +of a security issue, you may file a public GitHub issue on the project +`issue tracker`_. + +Known Vulnerabilities +--------------------- + +The following are known vulnerabilities for older, unsupported versions of PyKMIP. + ++---------------------+--------------------------+-------------------+--------------------------+ +| CVE | Brief Description | PyKMIP Version(s) | Mitigation | ++=====================+==========================+===================+==========================+ +| `CVE-2018-1000872`_ | Server Denial-of-Service | <=0.7.0 | Upgrade to PyKMIP 0.8.0+ | ++---------------------+--------------------------+-------------------+--------------------------+ + +.. _`issue tracker`: https://github.com/OpenKMIP/PyKMIP/issues +.. _`CVE-2018-1000872`: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000872 \ No newline at end of file diff -Nru python-pykmip-0.9.1/docs/source/server.rst python-pykmip-0.10.0/docs/source/server.rst --- python-pykmip-0.9.1/docs/source/server.rst 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/docs/source/server.rst 2020-02-25 16:05:27.000000000 +0000 @@ -37,6 +37,7 @@ TLS_RSA_WITH_AES_256_CBC_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 logging_level=DEBUG + database_path=/tmp/pykmip.db The server can also be configured manually via Python. The following example shows how to create the ``KmipServer`` in Python code, directly specifying the @@ -61,7 +62,8 @@ ... 'TLS_RSA_WITH_AES_256_CBC_SHA256', ... 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384' ... ], - ... logging_level='DEBUG' + ... logging_level='DEBUG', + ... database_path='/tmp/pykmip.db' ... ) The different configuration options are defined below: @@ -178,9 +180,16 @@ If PyKMIP is installed and you are able to ``import kmip`` in Python, you can copy the startup script and run it from any directory you choose. +PyKMIP also defines a system-wide entry point that can be used to run the +PyKMIP server once PyKMIP is installed. You can use the entry point like this: + +.. code-block:: console + + $ pykmip-server + Storage ------- -All data storage for the server is managed via `sqlalchemy`_. The current +All data storage for the server is managed via `SQLAlchemy`_. The current backend leverages `SQLite`_, storing managed objects in a flat file. The file location can be configured using the ``database_path`` configuration setting. By default this file will be located at ``/tmp/pykmip.database``. If this @@ -193,7 +202,7 @@ is no upgrade path. Long term, the intent is to add support for more robust database and storage -backends available through ``sqlalchemy``. If you are interested in this work, +backends available through ``SQLAlchemy``. If you are interested in this work, please see :doc:`Development ` for more information. .. _authentication: @@ -761,6 +770,29 @@ ... "Example Private Key" ... ) +Split Keys +~~~~~~~~~~ +A split key is a secret value representing a key composed of multiple parts. +The parts of the key can be recombined cryptographically to reconstitute the +original key. + +Creating a split key object would look like this: + +.. code-block:: python + + >>> from kmip import enums + >>> from kmip.pie.objects import SplitKey + >>> key = SplitKey( + ... cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + ... cryptographic_length=128, + ... key_value=b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF', + ... name="Split Key", + ... split_key_parts=3, + ... key_part_identifier=1, + ... split_key_threshold=3, + ... split_key_method=enums.SplitKeyMethod.XOR + ... ) + Certificates ~~~~~~~~~~~~ A certificate is a cryptographic object that contains a public key along with @@ -839,6 +871,18 @@ If you are interested in adding a new cryptographic backend to the PyKMIP server, see :doc:`Development ` for more information. +Activate +~~~~~~~~ +The Activate operation updates the state of a managed object, allowing it to +be used for cryptographic operations. Specifically, the object transitions +from the pre-active state to the active state (see :term:`state`). + +Errors may be generated during the activation of a managed object. These +may occur in the following cases: + +* the managed object is not activatable (e.g., opaque data object) +* the managed object is not in the pre-active state + Create ~~~~~~ The Create operation is used to create symmetric keys for a variety of @@ -920,27 +964,26 @@ * an invalid cryptographic length is provided for a specific cryptographic algorithm -Register -~~~~~~~~ -The Register operation is used to store an existing KMIP object with the -server. For examples of the objects that can be stored, see :ref:`objects`. +Decrypt +~~~~~~~ +The Decrypt operations allows the client to decrypt data with an existing +managed object stored by the server. Both symmetric and asymmetric decryption +are supported. See :ref:`encrypt` above for information on supported algorithms +and the types of errors to expect from the server. -All users are allowed to register objects. There are no quotas currently -enforced by the server. +DeleteAttribute +~~~~~~~~~~~~~~~ +The DeleteAttribute operation allows the client to delete an attribute from an +existing managed object. -Various KMIP-defined attributes may be set when an object is registered. -These may include: +Errors may be generated during the attribute deletion process. These may occur +in the following cases: -* :term:`cryptographic_algorithm` -* :term:`cryptographic_length` -* :term:`cryptographic_usage_mask` -* :term:`initial_date` -* :term:`key_format_type` -* :term:`name` -* :term:`object_type` -* :term:`operation_policy_name` -* :term:`state` -* :term:`unique_identifier` +* the specified managed object does not exist +* the specified attribute may not be applicable to the specified managed object +* the specified attribute is not supported by the server +* the specified attribute cannot be deleted by the client +* the specified attribute could not be located for deletion on the specified managed object DeriveKey ~~~~~~~~~ @@ -973,15 +1016,56 @@ * the cryptographic length is not provided with the request * the requested cryptographic length is longer than the generated key -Locate -~~~~~~ -The Locate operation is used to identify managed objects that the user has -access to, according to specific filtering criteria. Currently, the server -only support object filtering based on the object :term:`name` attribute. +Destroy +~~~~~~~ +The Destroy operation deletes a managed object from the server. Once destroyed, +the object can no longer be retrieved or used for cryptographic operations. +An object can only be destroyed if it is in the pre-active or deactivated +states. -If no filtering values are provided, the server will return a list of -:term:`unique_identifier` values corresponding to all of the managed objects -the user has access to. +Errors may be generated during the destruction of a managed object. These +may occur in the following cases: + +* the managed object is not destroyable (e.g., the object does not exist) +* the managed object is in the active state + +DiscoverVersions +~~~~~~~~~~~~~~~~ +The DiscoverVersions operation allows the client to determine which versions +of the KMIP specification are supported by the server. + +.. _encrypt: + +Encrypt +~~~~~~~ +The Encrypt operation allows the client to encrypt data with an existing +managed object stored by the server. Both symmetric and asymmetric encryption +are supported: + +Symmetric Key Algorithms +************************ +* `3DES`_ +* `AES`_ +* `Blowfish`_ +* `Camellia`_ +* `CAST5`_ +* `IDEA`_ +* `RC4`_ + +Asymmetric Key Algorithms +************************* +* `RSA`_ + +Errors may be generated during the encryption. These may occur in the +following cases: + +* the encryption key is not accessible to the user +* the encryption key is not in the active state and must be activated +* the encryption key does not have the Encrypt bit set in its usage mask +* the requested encryption algorithm is not supported +* the specified encryption key is not compatible with the requested algorithm +* the requested encryption algorithm requires a block cipher mode +* the requested block cipher mode is not supported .. _get: @@ -1035,49 +1119,64 @@ of a managed object, the server will return a list of attribute names for attributes that can be accessed using the GetAttributes operation. -Activate -~~~~~~~~ -The Activate operation updates the state of a managed object, allowing it to -be used for cryptographic operations. Specifically, the object transitions -from the pre-active state to the active state (see :term:`state`). +Locate +~~~~~~ +The Locate operation is used to identify managed objects that the user has +access to, according to specific filtering criteria. Currently, the server +only support object filtering based on the object :term:`name` attribute. -Errors may be generated during the activation of a managed object. These -may occur in the following cases: +If no filtering values are provided, the server will return a list of +:term:`unique_identifier` values corresponding to all of the managed objects +the user has access to. -* the managed object is not activatable (e.g., opaque data object) -* the managed object is not in the pre-active state +MAC +~~~ +The MAC operation allows the client to compute a message authentication code +on data using an existing managed object stored by the server. Both `HMAC`_ +and `CMAC`_ algorithms are supported: -Revoke -~~~~~~ -The Revoke operation updates the state of a managed object, effectively -deactivating but not destroying it. The client provides a specific -:term:`revocation_reason_code` indicating why revocation is occurring. +HMAC Hashing Algorithms +*********************** +* `MD5`_ +* `SHA1`_ +* `SHA224`_ +* `SHA256`_ +* `SHA384`_ +* `SHA512`_ -If revocation is due to a key or CA compromise, the managed object is moved -to the compromised state if it is in the pre-active, active, or deactivated -states. If the object has already been destroyed, it will be moved to the -destroyed compromised state. Otherwise, if revocation is due to any other -reason, the managed object is moved to the deactivated state if it is in -the active state. +CMAC Symmetric Algorithms +************************* +* `3DES`_ +* `AES`_ +* `Blowfish`_ +* `Camellia`_ +* `CAST5`_ +* `IDEA`_ +* `RC4`_ -Errors may be generated during the revocation of a managed object. These +Errors may be generated during the authentication code creation process. These may occur in the following cases: -* the managed object is not revokable (e.g., opaque data object) -* the managed object is not active when revoked for a non-compromise +* the managed object to use is not accessible to the user +* the managed object to use is not in the active state and must be activated +* the managed object does not have the Generate bit set in its usage mask +* the requested algorithm is not supported for HMAC/CMAC generation -Destroy -~~~~~~~ -The Destroy operation deletes a managed object from the server. Once destroyed, -the object can no longer be retrieved or used for cryptographic operations. -An object can only be destroyed if it is in the pre-active or deactivated -states. +ModifyAttribute +~~~~~~~~~~~~~~~ +The ModifyAttribute operation allows the client to modify an existing attribute +on an existing managed object. -Errors may be generated during the destruction of a managed object. These -may occur in the following cases: +Errors may be generated during the attribute modification process. These may +occur in the following cases: -* the managed object is not destroyable (e.g., the object does not exist) -* the managed object is in the active state +* the specified managed object does not exist +* the specified attribute may not be applicable to the specified managed object +* the specified attribute is not supported by the server +* the specified attribute cannot be modified by the client +* the specified attribute is not set on the specified managed object +* the specified attribute is multivalued and the current attribute field must be specified +* the specified attribute index does not correspond to an existing attribute Query ~~~~~ @@ -1101,50 +1200,60 @@ The PyKMIP server currently only includes the supported operations and the server information in Query responses. -DiscoverVersions -~~~~~~~~~~~~~~~~ -The DiscoverVersions operation allows the client to determine which versions -of the KMIP specification are supported by the server. +Register +~~~~~~~~ +The Register operation is used to store an existing KMIP object with the +server. For examples of the objects that can be stored, see :ref:`objects`. -.. _encrypt: +All users are allowed to register objects. There are no quotas currently +enforced by the server. -Encrypt -~~~~~~~ -The Encrypt operation allows the client to encrypt data with an existing -managed object stored by the server. Both symmetric and asymmetric encryption -are supported: +Various KMIP-defined attributes may be set when an object is registered. +These may include: -Symmetric Key Algorithms -************************ -* `3DES`_ -* `AES`_ -* `Blowfish`_ -* `Camellia`_ -* `CAST5`_ -* `IDEA`_ -* `RC4`_ +* :term:`cryptographic_algorithm` +* :term:`cryptographic_length` +* :term:`cryptographic_usage_mask` +* :term:`initial_date` +* :term:`key_format_type` +* :term:`name` +* :term:`object_type` +* :term:`operation_policy_name` +* :term:`state` +* :term:`unique_identifier` -Asymmetric Key Algorithms -************************* -* `RSA`_ +Revoke +~~~~~~ +The Revoke operation updates the state of a managed object, effectively +deactivating but not destroying it. The client provides a specific +:term:`revocation_reason_code` indicating why revocation is occurring. -Errors may be generated during the encryption. These may occur in the -following cases: +If revocation is due to a key or CA compromise, the managed object is moved +to the compromised state if it is in the pre-active, active, or deactivated +states. If the object has already been destroyed, it will be moved to the +destroyed compromised state. Otherwise, if revocation is due to any other +reason, the managed object is moved to the deactivated state if it is in +the active state. -* the encryption key is not accessible to the user -* the encryption key is not in the active state and must be activated -* the encryption key does not have the Encrypt bit set in its usage mask -* the requested encryption algorithm is not supported -* the specified encryption key is not compatible with the requested algorithm -* the requested encryption algorithm requires a block cipher mode -* the requested block cipher mode is not supported +Errors may be generated during the revocation of a managed object. These +may occur in the following cases: -Decrypt -~~~~~~~ -The Decrypt operations allows the client to decrypt data with an existing -managed object stored by the server. Both symmetric and asymmetric decryption -are supported. See :ref:`encrypt` above for information on supported algorithms -and the types of errors to expect from the server. +* the managed object is not revokable (e.g., opaque data object) +* the managed object is not active when revoked for a non-compromise + +SetAttribute +~~~~~~~~~~~~ +The SetAttribute operation allows the client to set the value of an attribute +on an existing managed object. + +Errors may be generated during the attribute setting process. These may occur +in the following cases: + +* the specified managed object does not exist +* the specified attribute may not be applicable to the specified managed object +* the specified attribute is not supported by the server +* the specified attribute cannot be set by the client +* the specified attribute is multivalued and cannot be set with this operation .. _sign: @@ -1181,41 +1290,8 @@ information on supported algorithms and the types of errors to expect from the server. -MAC -~~~ -The MAC operation allows the client to compute a message authentication code -on data using an existing managed object stored by the server. Both `HMAC`_ -and `CMAC`_ algorithms are supported: - -HMAC Hashing Algorithms -*********************** -* `MD5`_ -* `SHA1`_ -* `SHA224`_ -* `SHA256`_ -* `SHA384`_ -* `SHA512`_ - -CMAC Symmetric Algorithms -************************* -* `3DES`_ -* `AES`_ -* `Blowfish`_ -* `Camellia`_ -* `CAST5`_ -* `IDEA`_ -* `RC4`_ - -Errors may be generated during the authentication code creation process. These -may occur in the following cases: - -* the managed object to use is not accessible to the user -* the managed object to use is not in the active state and must be activated -* the managed object does not have the Generate bit set in its usage mask -* the requested algorithm is not supported for HMAC/CMAC generation - .. _`ssl`: https://docs.python.org/dev/library/ssl.html#socket-creation -.. _`sqlalchemy`: https://www.sqlalchemy.org/ +.. _`SQLAlchemy`: https://www.sqlalchemy.org/ .. _`SQLite`: http://docs.sqlalchemy.org/en/latest/dialects/sqlite.html .. _`pyca/cryptography`: https://cryptography.io/en/latest/ .. _`OpenSSL`: https://www.openssl.org/ diff -Nru python-pykmip-0.9.1/kmip/core/attributes.py python-pykmip-0.10.0/kmip/core/attributes.py --- python-pykmip-0.9.1/kmip/core/attributes.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/attributes.py 2020-02-25 16:05:27.000000000 +0000 @@ -16,14 +16,15 @@ import six from kmip.core import enums -from kmip.core import exceptions - from kmip.core.enums import HashingAlgorithm as HashingAlgorithmEnum from kmip.core.enums import KeyFormatType as KeyFormatTypeEnum from kmip.core.enums import Tags +from kmip.core import exceptions + from kmip.core.misc import KeyFormatType +from kmip.core import primitives from kmip.core.primitives import Boolean from kmip.core.primitives import ByteString from kmip.core.primitives import Enumeration @@ -31,7 +32,9 @@ from kmip.core.primitives import Struct from kmip.core.primitives import TextString +from kmip.core import utils from kmip.core.utils import BytearrayStream + from enum import Enum @@ -1063,55 +1066,7 @@ super(State, self).__init__(enums.State, value, Tags.STATE) -# 3.33 -class ObjectGroup(TextString): - - def __init__(self, value=None): - super(ObjectGroup, self).__init__(value, Tags.OBJECT_GROUP) - - -# 3.36 -class ApplicationNamespace(TextString): - """ - The name of a namespace supported by the KMIP server. - - A part of ApplicationSpecificInformation, sets of these are also potential - responses to a Query request. See Sections 3.36 and 4.25 of the KMIP v1.1 - specification for more information. - """ - - def __init__(self, value=None): - """ - Construct an ApplicationNamespace object. - - Args: - value (str): A string representing a namespace. Optional, defaults - to None. - """ - super(ApplicationNamespace, self).__init__( - value, Tags.APPLICATION_NAMESPACE) - - -class ApplicationData(TextString): - """ - A string representing data specific to an application namespace. - - A part of ApplicationSpecificInformation. See Section 3.36 of the KMIP v1.1 - specification for more information. - """ - - def __init__(self, value=None): - """ - Construct an ApplicationData object. - - Args: - value (str): A string representing data for a particular namespace. - Optional, defaults to None. - """ - super(ApplicationData, self).__init__(value, Tags.APPLICATION_DATA) - - -class ApplicationSpecificInformation(Struct): +class ApplicationSpecificInformation(primitives.Struct): """ A structure used to store data specific to the applications that use a Managed Object. @@ -1131,98 +1086,168 @@ Construct an ApplicationSpecificInformation object. Args: - application_namespace (ApplicationNamespace): The name of a - namespace supported by the server. Optional, defaults to None. - application_data (ApplicationData): String data relevant to the - specified namespace. Optional, defaults to None. + application_namespace (string): The name of a namespace supported + by the server. Optional, defaults to None. Required for + read/write. + application_data (string): String data relevant to the specified + namespace. Optional, defaults to None. Required for read/write. """ super(ApplicationSpecificInformation, self).__init__( - Tags.APPLICATION_SPECIFIC_INFORMATION) + enums.Tags.APPLICATION_SPECIFIC_INFORMATION + ) - if application_namespace is None: - self.application_namespace = ApplicationNamespace() - else: - self.application_namespace = application_namespace + self._application_namespace = None + self._application_data = None - if application_data is None: - self.application_data = ApplicationData() + self.application_namespace = application_namespace + self.application_data = application_data + + @property + def application_namespace(self): + if self._application_namespace: + return self._application_namespace.value + return None + + @application_namespace.setter + def application_namespace(self, value): + if value is None: + self._application_namespace = None + elif isinstance(value, six.string_types): + self._application_namespace = primitives.TextString( + value=value, + tag=enums.Tags.APPLICATION_NAMESPACE + ) else: - self.application_data = application_data + raise TypeError("The application namespace must be a string.") - self.validate() + @property + def application_data(self): + if self._application_data: + return self._application_data.value + return None - def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0): + @application_data.setter + def application_data(self, value): + if value is None: + self._application_data = None + elif isinstance(value, six.string_types): + self._application_data = primitives.TextString( + value=value, + tag=enums.Tags.APPLICATION_DATA + ) + else: + raise TypeError("The application data must be a string.") + + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): """ - Read the data encoding the ApplicationSpecificInformation object and - decode it into its constituent parts. + Read the data encoding the ApplicationSpecificInformation attribute + and decode it. Args: - istream (Stream): A data stream containing encoded object data, - supporting a read method; usually a BytearrayStream object. + input_buffer (stream): A data stream containing encoded object + data, supporting a read method; usually a BytearrayStream + object. kmip_version (KMIPVersion): An enumeration defining the KMIP version with which the object will be decoded. Optional, defaults to KMIP 1.0. """ super(ApplicationSpecificInformation, self).read( - istream, + input_buffer, kmip_version=kmip_version ) - tstream = BytearrayStream(istream.read(self.length)) + local_buffer = utils.BytearrayStream(input_buffer.read(self.length)) - self.application_namespace.read(tstream, kmip_version=kmip_version) - self.application_data.read(tstream, kmip_version=kmip_version) + if self.is_tag_next(enums.Tags.APPLICATION_NAMESPACE, local_buffer): + self._application_namespace = primitives.TextString( + tag=enums.Tags.APPLICATION_NAMESPACE + ) + self._application_namespace.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The ApplicationSpecificInformation encoding is missing the " + "ApplicationNamespace field." + ) + if self.is_tag_next(enums.Tags.APPLICATION_DATA, local_buffer): + self._application_data = primitives.TextString( + tag=enums.Tags.APPLICATION_DATA + ) + self._application_data.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The ApplicationSpecificInformation encoding is missing the " + "ApplicationData field." + ) - self.is_oversized(tstream) - self.validate() + self.is_oversized(local_buffer) - def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0): + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): """ Write the data encoding the ApplicationSpecificInformation object to a - stream. + buffer. Args: - ostream (Stream): A data stream in which to encode object data, - supporting a write method; usually a BytearrayStream object. + output_buffer (stream): A data stream in which to encode object + data, supporting a write method; usually a BytearrayStream + object. kmip_version (KMIPVersion): An enumeration defining the KMIP version with which the object will be encoded. Optional, defaults to KMIP 1.0. """ - tstream = BytearrayStream() + local_buffer = utils.BytearrayStream() - self.application_namespace.write(tstream, kmip_version=kmip_version) - self.application_data.write(tstream, kmip_version=kmip_version) + if self._application_namespace: + self._application_namespace.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The ApplicationSpecificInformation object is missing the " + "ApplicationNamespace field." + ) + if self._application_data: + self._application_data.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The ApplicationSpecificInformation object is missing the " + "ApplicationData field." + ) - self.length = tstream.length() + self.length = local_buffer.length() super(ApplicationSpecificInformation, self).write( - ostream, + output_buffer, kmip_version=kmip_version ) - ostream.write(tstream.buffer) - - def validate(self): - """ - Error check the types of the different attributes of the - ApplicationSpecificInformation object. - """ - self.__validate() + output_buffer.write(local_buffer.buffer) def __repr__(self): - application_namespace = "application_namespace={0}".format( - repr(self.application_namespace) - ) - application_data = "application_data={0}".format( - repr(self.application_data) - ) - return "ApplicationSpecificInformation({0}, {1})".format( - application_namespace, - application_data - ) + args = [ + "application_namespace={}".format( + repr(self.application_namespace) + ), + "application_data={}".format(repr(self.application_data)) + ] + return "ApplicationSpecificInformation({})".format(", ".join(args)) def __str__(self): - return str({ - "application_namespace": str(self.application_namespace), - "application_data": str(self.application_data) - }) + value = ", ".join( + [ + '"application_namespace": "{}"'.format( + self.application_namespace + ), + '"application_data": "{}"'.format(self.application_data) + ] + ) + return "{" + value + "}" def __eq__(self, other): if isinstance(other, ApplicationSpecificInformation): @@ -1240,45 +1265,6 @@ else: return NotImplemented - def __validate(self): - if not isinstance(self.application_namespace, ApplicationNamespace): - msg = "invalid application namespace" - msg += "; expected {0}, received {1}".format( - ApplicationNamespace, self.application_namespace) - raise TypeError(msg) - - if not isinstance(self.application_data, ApplicationData): - msg = "invalid application data" - msg += "; expected {0}, received {1}".format( - ApplicationData, self.application_data) - raise TypeError(msg) - - @classmethod - def create(cls, application_namespace, application_data): - """ - Construct an ApplicationSpecificInformation object from provided data - and namespace values. - - Args: - application_namespace (str): The name of the application namespace. - application_data (str): Application data related to the namespace. - - Returns: - ApplicationSpecificInformation: The newly created set of - application information. - - Example: - >>> x = ApplicationSpecificInformation.create('namespace', 'data') - >>> x.application_namespace.value - 'namespace' - >>> x.application_data.value - 'data' - """ - namespace = ApplicationNamespace(application_namespace) - data = ApplicationData(application_data) - return ApplicationSpecificInformation( - application_namespace=namespace, application_data=data) - # 3.37 class ContactInformation(TextString): diff -Nru python-pykmip-0.9.1/kmip/core/enums.py python-pykmip-0.10.0/kmip/core/enums.py --- python-pykmip-0.9.1/kmip/core/enums.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/enums.py 2020-02-25 16:05:27.000000000 +0000 @@ -126,6 +126,7 @@ KEY_VALUE_PRESENT = 'Key Value Present' KEY_VALUE_LOCATION = 'Key Value Location' ORIGINAL_CREATION_DATE = 'Original Creation Date' + SENSITIVE = "Sensitive" class AuthenticationSuite(enum.Enum): diff -Nru python-pykmip-0.9.1/kmip/core/exceptions.py python-pykmip-0.10.0/kmip/core/exceptions.py --- python-pykmip-0.9.1/kmip/core/exceptions.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/exceptions.py 2020-02-25 16:05:27.000000000 +0000 @@ -228,6 +228,23 @@ ) +class OperationFailure(KmipError): + """ + An exception raised upon the failure of a KMIP appliance operation. + """ + def __init__(self, status, reason, message): + """ + Construct the error message and attributes for the KMIP operation + failure. + + Args: + status: a ResultStatus enumeration + reason: a ResultReason enumeration + message: a string providing additional error information + """ + super(OperationFailure, self).__init__(status, reason, message) + + class OperationNotSupported(KmipError): """ An error generated when an unsupported operation is invoked. diff -Nru python-pykmip-0.9.1/kmip/core/factories/attribute_values.py python-pykmip-0.10.0/kmip/core/factories/attribute_values.py --- python-pykmip-0.9.1/kmip/core/factories/attribute_values.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/factories/attribute_values.py 2020-02-25 16:05:27.000000000 +0000 @@ -38,7 +38,11 @@ elif name is enums.AttributeType.CRYPTOGRAPHIC_DOMAIN_PARAMETERS: raise NotImplementedError() elif name is enums.AttributeType.CERTIFICATE_TYPE: - raise NotImplementedError() + return primitives.Enumeration( + enums.CertificateType, + value=value, + tag=enums.Tags.CERTIFICATE_TYPE + ) elif name is enums.AttributeType.CERTIFICATE_LENGTH: return primitives.Integer(value, enums.Tags.CERTIFICATE_LENGTH) elif name is enums.AttributeType.X_509_CERTIFICATE_IDENTIFIER: @@ -89,7 +93,7 @@ elif name is enums.AttributeType.ARCHIVE_DATE: return primitives.DateTime(value, enums.Tags.ARCHIVE_DATE) elif name is enums.AttributeType.OBJECT_GROUP: - return self._create_object_group(value) + return primitives.TextString(value, enums.Tags.OBJECT_GROUP) elif name is enums.AttributeType.FRESH: return primitives.Boolean(value, enums.Tags.FRESH) elif name is enums.AttributeType.LINK: @@ -100,6 +104,8 @@ return self._create_contact_information(value) elif name is enums.AttributeType.LAST_CHANGE_DATE: return primitives.DateTime(value, enums.Tags.LAST_CHANGE_DATE) + elif name is enums.AttributeType.SENSITIVE: + return primitives.Boolean(value, enums.Tags.SENSITIVE) elif name is enums.AttributeType.CUSTOM_ATTRIBUTE: return attributes.CustomAttribute(value) else: @@ -178,6 +184,7 @@ elif enum is enums.Tags.ARCHIVE_DATE: return primitives.DateTime(value, enums.Tags.ARCHIVE_DATE) elif enum is enums.Tags.OBJECT_GROUP: + return primitives.TextString(value, enums.Tags.OBJECT_GROUP) return self._create_object_group(value) elif enum is enums.Tags.FRESH: return primitives.Boolean(value, enums.Tags.FRESH) @@ -189,6 +196,8 @@ return self._create_contact_information(value) elif enum is enums.Tags.LAST_CHANGE_DATE: return primitives.DateTime(value, enums.Tags.LAST_CHANGE_DATE) + elif enum is enums.Tags.SENSITIVE: + return primitives.Boolean(value, enums.Tags.SENSITIVE) elif enum is enums.Tags.CUSTOM_ATTRIBUTE: return attributes.CustomAttribute(value) else: @@ -263,38 +272,14 @@ return attributes.CryptographicUsageMask(mask) - def _create_object_group(self, group): - if group is not None and not isinstance(group, str): - msg = utils.build_er_error(attributes.ObjectGroup, - 'constructor argument type', str, - type(group)) - raise TypeError(msg) - - return attributes.ObjectGroup(group) - def _create_application_specific_information(self, info): - if info is None: - return attributes.ApplicationSpecificInformation() + if info: + return attributes.ApplicationSpecificInformation( + application_namespace=info.get("application_namespace"), + application_data=info.get("application_data") + ) else: - application_namespace = info.get('application_namespace') - application_data = info.get('application_data') - - if not isinstance(application_namespace, str): - msg = utils.build_er_error( - attributes.ApplicationSpecificInformation, - 'constructor argument type', - str, type(application_namespace)) - raise TypeError(msg) - - if not isinstance(application_data, str): - msg = utils.build_er_error( - attributes.ApplicationSpecificInformation, - 'constructor argument type', - str, type(application_data)) - raise TypeError(msg) - - return attributes.ApplicationSpecificInformation.create( - application_namespace, application_data) + return attributes.ApplicationSpecificInformation() def _create_contact_information(self, info): if info is None: diff -Nru python-pykmip-0.9.1/kmip/core/factories/payloads/__init__.py python-pykmip-0.10.0/kmip/core/factories/payloads/__init__.py --- python-pykmip-0.9.1/kmip/core/factories/payloads/__init__.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/factories/payloads/__init__.py 2020-02-25 16:05:27.000000000 +0000 @@ -46,6 +46,8 @@ return self._create_get_attribute_list_payload() elif operation is enums.Operation.ADD_ATTRIBUTE: return self._create_add_attribute_payload() + elif operation is enums.Operation.SET_ATTRIBUTE: + return self._create_set_attribute_payload() elif operation is enums.Operation.MODIFY_ATTRIBUTE: return self._create_modify_attribute_payload() elif operation is enums.Operation.DELETE_ATTRIBUTE: @@ -144,6 +146,9 @@ def _create_add_attribute_payload(self): raise NotImplementedError() + def _create_set_attribute_payload(self): + raise NotImplementedError() + def _create_modify_attribute_payload(self): raise NotImplementedError() diff -Nru python-pykmip-0.9.1/kmip/core/factories/payloads/request.py python-pykmip-0.10.0/kmip/core/factories/payloads/request.py --- python-pykmip-0.9.1/kmip/core/factories/payloads/request.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/factories/payloads/request.py 2020-02-25 16:05:27.000000000 +0000 @@ -19,6 +19,7 @@ class RequestPayloadFactory(PayloadFactory): + # TODO (peterhamilton) Alphabetize these def _create_create_payload(self): return payloads.CreateRequestPayload() @@ -52,6 +53,15 @@ def _create_get_attributes_payload(self): return payloads.GetAttributesRequestPayload() + def _create_delete_attribute_payload(self): + return payloads.DeleteAttributeRequestPayload() + + def _create_set_attribute_payload(self): + return payloads.SetAttributeRequestPayload() + + def _create_modify_attribute_payload(self): + return payloads.ModifyAttributeRequestPayload() + def _create_destroy_payload(self): return payloads.DestroyRequestPayload() diff -Nru python-pykmip-0.9.1/kmip/core/factories/payloads/response.py python-pykmip-0.10.0/kmip/core/factories/payloads/response.py --- python-pykmip-0.9.1/kmip/core/factories/payloads/response.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/factories/payloads/response.py 2020-02-25 16:05:27.000000000 +0000 @@ -19,6 +19,7 @@ class ResponsePayloadFactory(PayloadFactory): + # TODO (peterhamilton) Alphabetize these def _create_create_payload(self): return payloads.CreateResponsePayload() @@ -52,6 +53,15 @@ def _create_get_attributes_payload(self): return payloads.GetAttributesResponsePayload() + def _create_delete_attribute_payload(self): + return payloads.DeleteAttributeResponsePayload() + + def _create_set_attribute_payload(self): + return payloads.SetAttributeResponsePayload() + + def _create_modify_attribute_payload(self): + return payloads.ModifyAttributeResponsePayload() + def _create_destroy_payload(self): return payloads.DestroyResponsePayload() diff -Nru python-pykmip-0.9.1/kmip/core/factories/secrets.py python-pykmip-0.10.0/kmip/core/factories/secrets.py --- python-pykmip-0.9.1/kmip/core/factories/secrets.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/factories/secrets.py 2020-02-25 16:05:27.000000000 +0000 @@ -31,6 +31,7 @@ from kmip.core.secrets import PrivateKey from kmip.core.secrets import PublicKey from kmip.core.secrets import SecretData +from kmip.core.secrets import SplitKey from kmip.core.secrets import SymmetricKey from kmip.core.secrets import Template @@ -116,7 +117,18 @@ return PrivateKey(key_block) def _create_split_key(self, value): - raise NotImplementedError() + if value is None: + return SplitKey() + else: + key_block = self._build_key_block(value) + return SplitKey( + split_key_parts=value.get("split_key_parts"), + key_part_identifier=value.get("key_part_identifier"), + split_key_threshold=value.get("split_key_threshold"), + split_key_method=value.get("split_key_method"), + prime_field_size=value.get("prime_field_size"), + key_block=key_block + ) def _create_template(self, value): if value is None: diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/activate.py python-pykmip-0.10.0/kmip/core/messages/payloads/activate.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/activate.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/activate.py 2020-02-25 16:05:27.000000000 +0000 @@ -15,13 +15,11 @@ from kmip.core import attributes from kmip.core import enums - -from kmip.core.primitives import Struct - from kmip.core.utils import BytearrayStream +from kmip.core.messages.payloads import base -class ActivateRequestPayload(Struct): +class ActivateRequestPayload(base.RequestPayload): """ A request payload for the Activate operation. @@ -40,8 +38,7 @@ unique_identifier (UniqueIdentifier): The UUID of a managed cryptographic object. """ - super(ActivateRequestPayload, self).__init__( - tag=enums.Tags.REQUEST_PAYLOAD) + super(ActivateRequestPayload, self).__init__() self.unique_identifier = unique_identifier self.validate() @@ -103,7 +100,7 @@ raise TypeError(msg) -class ActivateResponsePayload(Struct): +class ActivateResponsePayload(base.ResponsePayload): """ A response payload for the Activate operation. @@ -122,8 +119,7 @@ unique_identifier (UniqueIdentifier): The UUID of a managed cryptographic object. """ - super(ActivateResponsePayload, self).__init__( - tag=enums.Tags.RESPONSE_PAYLOAD) + super(ActivateResponsePayload, self).__init__() if unique_identifier is None: self.unique_identifier = attributes.UniqueIdentifier() else: diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/archive.py python-pykmip-0.10.0/kmip/core/messages/payloads/archive.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/archive.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/archive.py 2020-02-25 16:05:27.000000000 +0000 @@ -18,9 +18,10 @@ from kmip import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class ArchiveRequestPayload(primitives.Struct): +class ArchiveRequestPayload(base.RequestPayload): """ A request payload for the Archive operation. @@ -36,9 +37,7 @@ unique_identifier (string): The ID of the managed object (e.g., a public key) to archive. Optional, defaults to None. """ - super(ArchiveRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(ArchiveRequestPayload, self).__init__() self._unique_identifier = None self.unique_identifier = unique_identifier @@ -151,7 +150,7 @@ }) -class ArchiveResponsePayload(primitives.Struct): +class ArchiveResponsePayload(base.ResponsePayload): """ A response payload for the Archive operation. @@ -167,9 +166,7 @@ unique_identifier (string): The ID of the managed object (e.g., a public key) that was archived. Optional, defaults to None. """ - super(ArchiveResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(ArchiveResponsePayload, self).__init__() self._unique_identifier = None self.unique_identifier = unique_identifier diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/base.py python-pykmip-0.10.0/kmip/core/messages/payloads/base.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/base.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/base.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,34 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from kmip.core import enums +from kmip.core import primitives + + +class RequestPayload(primitives.Struct): + """ + An abstract base class for KMIP request payloads. + """ + def __init__(self): + super(RequestPayload, self).__init__(enums.Tags.REQUEST_PAYLOAD) + + +class ResponsePayload(primitives.Struct): + """ + An abstract base class for KMIP response payloads. + """ + + def __init__(self): + super(ResponsePayload, self).__init__(enums.Tags.RESPONSE_PAYLOAD) diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/cancel.py python-pykmip-0.10.0/kmip/core/messages/payloads/cancel.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/cancel.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/cancel.py 2020-02-25 16:05:27.000000000 +0000 @@ -18,9 +18,10 @@ from kmip import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class CancelRequestPayload(primitives.Struct): +class CancelRequestPayload(base.RequestPayload): """ A request payload for the Cancel operation. @@ -37,9 +38,7 @@ asynchronous_correlation_value (bytes): The ID of a pending operation to cancel, in bytes. Optional, defaults to None. """ - super(CancelRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(CancelRequestPayload, self).__init__() self._asynchronous_correlation_value = None self.asynchronous_correlation_value = asynchronous_correlation_value @@ -159,7 +158,7 @@ }) -class CancelResponsePayload(primitives.Struct): +class CancelResponsePayload(base.ResponsePayload): """ A response payload for the Cancel operation. @@ -183,9 +182,7 @@ specifying the result of canceling the operation. Optional, defaults to None. """ - super(CancelResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(CancelResponsePayload, self).__init__() self._asynchronous_correlation_value = None self._cancellation_result = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/check.py python-pykmip-0.10.0/kmip/core/messages/payloads/check.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/check.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/check.py 2020-02-25 16:05:27.000000000 +0000 @@ -18,9 +18,10 @@ from kmip import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class CheckRequestPayload(primitives.Struct): +class CheckRequestPayload(base.RequestPayload): """ A request payload for the Check operation. @@ -55,7 +56,7 @@ lease should be available for on the checked object. Optional, defaults to None. """ - super(CheckRequestPayload, self).__init__(enums.Tags.REQUEST_PAYLOAD) + super(CheckRequestPayload, self).__init__() self._unique_identifier = None self._usage_limits_count = None @@ -285,7 +286,7 @@ }) -class CheckResponsePayload(primitives.Struct): +class CheckResponsePayload(base.ResponsePayload): """ A response payload for the Check operation. @@ -320,7 +321,7 @@ lease should be available for on the checked object. Optional, defaults to None. """ - super(CheckResponsePayload, self).__init__(enums.Tags.RESPONSE_PAYLOAD) + super(CheckResponsePayload, self).__init__() self._unique_identifier = None self._usage_limits_count = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/create_key_pair.py python-pykmip-0.10.0/kmip/core/messages/payloads/create_key_pair.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/create_key_pair.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/create_key_pair.py 2020-02-25 16:05:27.000000000 +0000 @@ -20,9 +20,10 @@ from kmip.core import objects from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class CreateKeyPairRequestPayload(primitives.Struct): +class CreateKeyPairRequestPayload(base.RequestPayload): """ A request payload for the CreateKeyPair operation. @@ -80,9 +81,7 @@ permissible for the new public key. Added in KMIP 2.0. Optional, defaults to None. """ - super(CreateKeyPairRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(CreateKeyPairRequestPayload, self).__init__() self._common_template_attribute = None self._private_key_template_attribute = None @@ -518,7 +517,7 @@ return '{' + value + '}' -class CreateKeyPairResponsePayload(primitives.Struct): +class CreateKeyPairResponsePayload(base.ResponsePayload): """ A response payload for the CreateKeyPair operation. @@ -556,9 +555,7 @@ tag containing the set of attributes that were set on the new public key. Optional, defaults to None. """ - super(CreateKeyPairResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(CreateKeyPairResponsePayload, self).__init__() self._private_key_unique_identifier = None self._public_key_unique_identifier = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/create.py python-pykmip-0.10.0/kmip/core/messages/payloads/create.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/create.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/create.py 2020-02-25 16:05:27.000000000 +0000 @@ -20,9 +20,10 @@ from kmip.core import objects from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class CreateRequestPayload(primitives.Struct): +class CreateRequestPayload(base.RequestPayload): """ A request payload for the Create operation. @@ -52,9 +53,7 @@ structure containing the storage masks permissible for the new object. Added in KMIP 2.0. Optional, defaults to None. """ - super(CreateRequestPayload, self).__init__( - tag=enums.Tags.REQUEST_PAYLOAD - ) + super(CreateRequestPayload, self).__init__() self._object_type = None self._template_attribute = None @@ -316,7 +315,7 @@ return '{' + value + '}' -class CreateResponsePayload(primitives.Struct): +class CreateResponsePayload(base.ResponsePayload): """ A response payload for the Create operation. @@ -344,9 +343,7 @@ structure containing a set of attributes that were set on the new object. Optional, defaults to None. """ - super(CreateResponsePayload, self).__init__( - tag=enums.Tags.RESPONSE_PAYLOAD - ) + super(CreateResponsePayload, self).__init__() self._object_type = None self._unique_identifier = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/decrypt.py python-pykmip-0.10.0/kmip/core/messages/payloads/decrypt.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/decrypt.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/decrypt.py 2020-02-25 16:05:27.000000000 +0000 @@ -19,9 +19,10 @@ from kmip.core import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class DecryptRequestPayload(primitives.Struct): +class DecryptRequestPayload(base.RequestPayload): """ A request payload for the Decrypt operation. @@ -33,13 +34,22 @@ data: The data to be decrypted in the form of a binary string. iv_counter_nonce: An IV/counter/nonce to be used with the decryption algorithm. Comes in the form of a binary string. + auth_additional_data: Any additional data to be authenticated via the + Authenticated Encryption Tag. Added in KMIP 1.4. + auth_tag: Specifies the tag that will be needed to + authenticate the decrypted data. Only returned on completion + of the encryption of the last of the plaintext by an + authenticated encryption cipher. Optional, defaults to None. + Added in KMIP 1.4. """ def __init__(self, unique_identifier=None, cryptographic_parameters=None, data=None, - iv_counter_nonce=None): + iv_counter_nonce=None, + auth_additional_data=None, + auth_tag=None): """ Construct a Decrypt request payload struct. @@ -56,20 +66,30 @@ encoding and decoding. iv_counter_nonce (bytes): The IV/counter/nonce value to be used with the decryption algorithm. Optional, defaults to None. + auth_additional_data (bytes): Any additional data to be + authenticated via the Authenticated Encryption Tag. + Added in KMIP 1.4. + auth_tag (bytes): Specifies the tag that will be needed to + authenticate the decrypted data. Only returned on completion + of the encryption of the last of the plaintext by an + authenticated encryption cipher. Optional, defaults to None. + Added in KMIP 1.4. """ - super(DecryptRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(DecryptRequestPayload, self).__init__() self._unique_identifier = None self._cryptographic_parameters = None self._data = None self._iv_counter_nonce = None + self._auth_additional_data = None + self._auth_tag = None self.unique_identifier = unique_identifier self.cryptographic_parameters = cryptographic_parameters self.data = data self.iv_counter_nonce = iv_counter_nonce + self.auth_additional_data = auth_additional_data + self.auth_tag = auth_tag @property def unique_identifier(self): @@ -144,6 +164,44 @@ else: raise TypeError("IV/counter/nonce must be bytes") + @property + def auth_additional_data(self): + if self._auth_additional_data: + return self._auth_additional_data.value + else: + return None + + @auth_additional_data.setter + def auth_additional_data(self, value): + if value is None: + self._auth_additional_data = None + elif isinstance(value, six.binary_type): + self._auth_additional_data = primitives.ByteString( + value=value, + tag=enums.Tags.AUTHENTICATED_ENCRYPTION_ADDITIONAL_DATA + ) + else: + raise TypeError("authenticated additional data must be bytes") + + @property + def auth_tag(self): + if self._auth_tag: + return self._auth_tag.value + else: + return None + + @auth_tag.setter + def auth_tag(self, value): + if value is None: + self._auth_tag = None + elif isinstance(value, six.binary_type): + self._auth_tag = primitives.ByteString( + value=value, + tag=enums.Tags.AUTHENTICATED_ENCRYPTION_TAG + ) + else: + raise TypeError("authenticated encryption tag must be bytes") + def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0): """ Read the data encoding the Decrypt request payload and decode it @@ -202,6 +260,29 @@ kmip_version=kmip_version ) + if kmip_version >= enums.KMIPVersion.KMIP_1_4: + if self.is_tag_next( + enums.Tags.AUTHENTICATED_ENCRYPTION_ADDITIONAL_DATA, + local_stream + ): + self._auth_additional_data = primitives.ByteString( + tag=enums.Tags.AUTHENTICATED_ENCRYPTION_ADDITIONAL_DATA + ) + self._auth_additional_data.read( + local_stream, + kmip_version=kmip_version + ) + if self.is_tag_next( + enums.Tags.AUTHENTICATED_ENCRYPTION_TAG, local_stream + ): + self._auth_tag = primitives.ByteString( + tag=enums.Tags.AUTHENTICATED_ENCRYPTION_TAG + ) + self._auth_tag.read( + local_stream, + kmip_version=kmip_version + ) + self.is_oversized(local_stream) def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0): @@ -243,6 +324,18 @@ kmip_version=kmip_version ) + if kmip_version >= enums.KMIPVersion.KMIP_1_4: + if self._auth_additional_data: + self._auth_additional_data.write( + local_stream, + kmip_version=kmip_version + ) + if self._auth_tag: + self._auth_tag.write( + local_stream, + kmip_version=kmip_version + ) + self.length = local_stream.length() super(DecryptRequestPayload, self).write( output_stream, @@ -261,6 +354,10 @@ return False elif self.iv_counter_nonce != other.iv_counter_nonce: return False + elif self.auth_additional_data != other.auth_additional_data: + return False + elif self.auth_tag != other.auth_tag: + return False else: return True else: @@ -279,7 +376,9 @@ repr(self.cryptographic_parameters) ), "data={0}".format(self.data), - "iv_counter_nonce={0}".format(self.iv_counter_nonce) + "iv_counter_nonce={0}".format(self.iv_counter_nonce), + "auth_additional_data={0}".format(self.auth_additional_data), + "auth_tag={0}".format(self.auth_tag) ]) return "DecryptRequestPayload({0})".format(args) @@ -288,11 +387,13 @@ 'unique_identifier': self.unique_identifier, 'cryptographic_parameters': self.cryptographic_parameters, 'data': self.data, - 'iv_counter_nonce': self.iv_counter_nonce + 'iv_counter_nonce': self.iv_counter_nonce, + 'auth_additional_data': self.auth_additional_data, + 'auth_tag': self.auth_tag }) -class DecryptResponsePayload(primitives.Struct): +class DecryptResponsePayload(base.ResponsePayload): """ A response payload for the Decrypt operation. @@ -315,9 +416,7 @@ data (bytes): The decrypted data in binary form. Required for encoding and decoding. """ - super(DecryptResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(DecryptResponsePayload, self).__init__() self._unique_identifier = None self._data = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/delete_attribute.py python-pykmip-0.10.0/kmip/core/messages/payloads/delete_attribute.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/delete_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/delete_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,568 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import six + +from kmip.core import enums +from kmip.core import exceptions +from kmip.core import objects +from kmip.core import primitives +from kmip.core import utils +from kmip.core.messages.payloads import base + + +class DeleteAttributeRequestPayload(base.RequestPayload): + """ + A request payload for the DeleteAttribute operation. + + Attributes: + unique_identifier: The unique ID of the object on which attribute + deletion should be performed. + attribute_name: The name of the attribute to be deleted. Used in + KMIP 1.0 - 1.4. + attribute_index: The index of the attribute to be deleted. Used in + KMIP 1.0 - 1.4. + current_attribute: The attribute to be deleted. Used in KMIP 2.0+. + attribute_reference: The reference to the attribute to be deleted. + Used in KMIP 2.0+. + """ + + def __init__(self, + unique_identifier=None, + attribute_name=None, + attribute_index=None, + current_attribute=None, + attribute_reference=None): + """ + Construct a DeleteAttribute request payload. + + Args: + unique_identifier (string): The unique ID of the object on which + attribute deletion should be performed. Optional, defaults to + None. + attribute_name (string): The name of the attribute to be deleted. + Used in KMIP 1.0 - 1.4. Defaults to None. Required for + read/write. + attribute_index (int): The index of the attribute to be deleted. + Used in KMIP 1.0 - 1.4. Optional, defaults to None. + current_attribute (struct): A CurrentAttribute structure containing + the attribute to be deleted. Used in KMIP 2.0+. Optional, + defaults to None. Must be specified if the attribute reference + is not provided. + attribute_reference (struct): An AttributeReference structure + containing a reference to the attribute to be deleted. Used in + KMIP 2.0+. Optional, defaults to None. Must be specified if the + current attribute is not specified. + """ + super(DeleteAttributeRequestPayload, self).__init__() + + self._unique_identifier = None + self._attribute_name = None + self._attribute_index = None + self._current_attribute = None + self._attribute_reference = None + + self.unique_identifier = unique_identifier + self.attribute_name = attribute_name + self.attribute_index = attribute_index + self.current_attribute = current_attribute + self.attribute_reference = attribute_reference + + @property + def unique_identifier(self): + if self._unique_identifier: + return self._unique_identifier.value + return None + + @unique_identifier.setter + def unique_identifier(self, value): + if value is None: + self._unique_identifier = None + elif isinstance(value, six.string_types): + self._unique_identifier = primitives.TextString( + value=value, + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + else: + raise TypeError("The unique identifier must be a string.") + + @property + def attribute_name(self): + if self._attribute_name: + return self._attribute_name.value + return None + + @attribute_name.setter + def attribute_name(self, value): + if value is None: + self._attribute_name = None + elif isinstance(value, six.string_types): + self._attribute_name = primitives.TextString( + value=value, + tag=enums.Tags.ATTRIBUTE_NAME + ) + else: + raise TypeError("The attribute name must be a string.") + + @property + def attribute_index(self): + if self._attribute_index: + return self._attribute_index.value + return None + + @attribute_index.setter + def attribute_index(self, value): + if value is None: + self._attribute_index = None + elif isinstance(value, six.integer_types): + self._attribute_index = primitives.Integer( + value=value, + tag=enums.Tags.ATTRIBUTE_INDEX + ) + else: + raise TypeError("The attribute index must be an integer.") + + @property + def current_attribute(self): + if self._current_attribute: + return self._current_attribute + return None + + @current_attribute.setter + def current_attribute(self, value): + if value is None: + self._current_attribute = None + elif isinstance(value, objects.CurrentAttribute): + self._current_attribute = value + else: + raise TypeError( + "The current attribute must be a CurrentAttribute object." + ) + + @property + def attribute_reference(self): + if self._attribute_reference: + return self._attribute_reference + return None + + @attribute_reference.setter + def attribute_reference(self, value): + if value is None: + self._attribute_reference = None + elif isinstance(value, objects.AttributeReference): + self._attribute_reference = value + else: + raise TypeError( + "The attribute reference must be an AttributeReference object." + ) + + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Read the data encoding the DeleteAttribute request payload and decode + it into its constituent part. + + Args: + input_buffer (stream): A data stream containing encoded object + data, supporting a read method; usually a BytearrayStream + object. + kmip_version (KMIPVersion): An enumeration defining the KMIP + version with which the object will be decoded. Optional, + defaults to KMIP 1.0. + + Raises: + InvalidKmipEncoding: Raised if fields are missing from the + encoding. + """ + super(DeleteAttributeRequestPayload, self).read( + input_buffer, + kmip_version=kmip_version + ) + local_buffer = utils.BytearrayStream(input_buffer.read(self.length)) + + if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_buffer): + self._unique_identifier = primitives.TextString( + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + self._unique_identifier.read( + local_buffer, + kmip_version=kmip_version + ) + else: + self._unique_identifier = None + + if kmip_version < enums.KMIPVersion.KMIP_2_0: + if self.is_tag_next(enums.Tags.ATTRIBUTE_NAME, local_buffer): + self._attribute_name = primitives.TextString( + tag=enums.Tags.ATTRIBUTE_NAME + ) + self._attribute_name.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The DeleteAttribute request payload encoding is missing " + "the attribute name field." + ) + + if self.is_tag_next(enums.Tags.ATTRIBUTE_INDEX, local_buffer): + self._attribute_index = primitives.Integer( + tag=enums.Tags.ATTRIBUTE_INDEX + ) + self._attribute_index.read( + local_buffer, + kmip_version=kmip_version + ) + else: + self._attribute_index = None + else: + if self.is_tag_next(enums.Tags.CURRENT_ATTRIBUTE, local_buffer): + self._current_attribute = objects.CurrentAttribute() + self._current_attribute.read( + local_buffer, + kmip_version=kmip_version + ) + else: + self._current_attribute = None + + if self.is_tag_next(enums.Tags.ATTRIBUTE_REFERENCE, local_buffer): + self._attribute_reference = objects.AttributeReference() + self._attribute_reference.read( + local_buffer, + kmip_version=kmip_version + ) + else: + self._attribute_reference = None + + if self._current_attribute == self._attribute_reference: + raise exceptions.InvalidKmipEncoding( + "The DeleteAttribute encoding is missing either the " + "current attribute or the attribute reference field." + ) + + self.is_oversized(local_buffer) + + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Write the data encoding the DeleteAttribute request payload to a + stream. + + Args: + output_buffer (stream): A data stream in which to encode object + data, supporting a write method; usually a BytearrayStream + object. + kmip_version (KMIPVersion): An enumeration defining the KMIP + version with which the object will be encoded. Optional, + defaults to KMIP 1.0. + + Raises: + InvalidField + """ + local_buffer = utils.BytearrayStream() + + if self._unique_identifier: + self._unique_identifier.write( + local_buffer, + kmip_version=kmip_version + ) + + if kmip_version < enums.KMIPVersion.KMIP_2_0: + if self._attribute_name: + self._attribute_name.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The DeleteAttribute request payload is missing the " + "attribute name field." + ) + + if self._attribute_index: + self._attribute_index.write( + local_buffer, + kmip_version=kmip_version + ) + else: + if self._current_attribute == self._attribute_reference: + raise exceptions.InvalidField( + "The DeleteAttribute request payload is missing either " + "the current attribute or the attribute reference field." + ) + + if self._current_attribute: + self._current_attribute.write( + local_buffer, + kmip_version=kmip_version + ) + if self._attribute_reference: + self._attribute_reference.write( + local_buffer, + kmip_version=kmip_version + ) + + self.length = local_buffer.length() + super(DeleteAttributeRequestPayload, self).write( + output_buffer, + kmip_version=kmip_version + ) + output_buffer.write(local_buffer.buffer) + + def __repr__(self): + args = [ + "unique_identifier='{}'".format(self.unique_identifier), + "attribute_name='{}'".format(self.attribute_name), + "attribute_index={}".format(self.attribute_index), + "current_attribute={}".format(repr( + self.current_attribute + ) if self.current_attribute else None), + "attribute_reference={}".format(repr( + self.attribute_reference + ) if self.attribute_reference else None) + ] + return "DeleteAttributeRequestPayload({})".format(", ".join(args)) + + def __str__(self): + return str( + { + "unique_identifier": self.unique_identifier, + "attribute_name": self.attribute_name, + "attribute_index": self.attribute_index, + "current_attribute": str( + self.current_attribute + ) if self.current_attribute else None, + "attribute_reference": str( + self.attribute_reference + ) if self.attribute_reference else None + } + ) + + def __eq__(self, other): + if isinstance(other, DeleteAttributeRequestPayload): + if self.unique_identifier != other.unique_identifier: + return False + elif self.attribute_name != other.attribute_name: + return False + elif self.attribute_index != other.attribute_index: + return False + elif self.current_attribute != other.current_attribute: + return False + elif self.attribute_reference != other.attribute_reference: + return False + else: + return True + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, DeleteAttributeRequestPayload): + return not self.__eq__(other) + else: + return NotImplemented + + +class DeleteAttributeResponsePayload(base.ResponsePayload): + """ + A response payload for the DeleteAttribute operation. + + Attributes: + unique_identifier: The unique ID of the object on which attribute + deletion was performed. Optional, defaults to None. + attribute: The attribute object deleted from the managed object. Used + in KMIP 1.0 - 1.4. + """ + + def __init__(self, unique_identifier=None, attribute=None): + """ + Construct a DeleteAttribute response payload. + + Args: + unique_identifier (string): The unique ID of the object on + which attribute deletion was performed. Defaults to None. + Required for read/write. + attribute (struct): An Attribute object containing the attribute + that was deleted. Used in KMIP 1.0 - 1.4. Defaults to None. + Required for read/write. + """ + super(DeleteAttributeResponsePayload, self).__init__() + + self._unique_identifier = None + self._attribute = None + + self.unique_identifier = unique_identifier + self.attribute = attribute + + @property + def unique_identifier(self): + if self._unique_identifier: + return self._unique_identifier.value + return None + + @unique_identifier.setter + def unique_identifier(self, value): + if value is None: + self._unique_identifier = None + elif isinstance(value, six.string_types): + self._unique_identifier = primitives.TextString( + value=value, + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + else: + raise TypeError("The unique identifier must be a string.") + + @property + def attribute(self): + if self._attribute: + return self._attribute + return None + + @attribute.setter + def attribute(self, value): + if value is None: + self._attribute = None + elif isinstance(value, objects.Attribute): + self._attribute = value + else: + raise TypeError( + "The attribute must be an Attribute object." + ) + + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Read the data encoding the DeleteAttribute response payload and decode + it into its constituent parts. + + Args: + input_buffer (stream): A data stream containing encoded object + data, supporting a read method; usually a BytearrayStream + object. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be decoded. Optional, + defaults to KMIP 1.0. + + Raises: + InvalidKmipEncoding: Raised if any required fields are missing + from the encoding. + """ + super(DeleteAttributeResponsePayload, self).read( + input_buffer, + kmip_version=kmip_version + ) + local_buffer = utils.BytearrayStream(input_buffer.read(self.length)) + + if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_buffer): + self._unique_identifier = primitives.TextString( + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + self._unique_identifier.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The DeleteAttribute response payload encoding is missing the " + "unique identifier field." + ) + + if kmip_version < enums.KMIPVersion.KMIP_2_0: + if self.is_tag_next(enums.Tags.ATTRIBUTE, local_buffer): + self._attribute = objects.Attribute() + self._attribute.read(local_buffer, kmip_version=kmip_version) + else: + raise exceptions.InvalidKmipEncoding( + "The DeleteAttribute response payload encoding is missing " + "the attribute field." + ) + + self.is_oversized(local_buffer) + + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Write the data encoding the DeleteAttribute response payload to a + buffer. + + Args: + output_buffer (buffer): A data buffer in which to encode object + data, supporting a write method; usually a BytearrayStream + object. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be encoded. Optional, + defaults to KMIP 1.0. + + Raises: + InvalidField: Raised if a required field is missing from the + payload object. + """ + local_buffer = utils.BytearrayStream() + + if self._unique_identifier: + self._unique_identifier.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The DeleteAttribute response payload is missing the unique " + "identifier field." + ) + + if kmip_version < enums.KMIPVersion.KMIP_2_0: + if self._attribute: + self._attribute.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The DeleteAttribute response payload is missing the " + "attribute field." + ) + + self.length = local_buffer.length() + super(DeleteAttributeResponsePayload, self).write( + output_buffer, + kmip_version=kmip_version + ) + output_buffer.write(local_buffer.buffer) + + def __repr__(self): + args = [ + "unique_identifier='{}'".format(self.unique_identifier), + "attribute={}".format(repr(self.attribute)) + ] + return "DeleteAttributeResponsePayload({})".format(", ".join(args)) + + def __str__(self): + return str( + { + "unique_identifier": self.unique_identifier, + "attribute": str(self.attribute) + } + ) + + def __eq__(self, other): + if isinstance(other, DeleteAttributeResponsePayload): + if self.unique_identifier != other.unique_identifier: + return False + elif self.attribute != other.attribute: + return False + else: + return True + return NotImplemented + + def __ne__(self, other): + if isinstance(other, DeleteAttributeResponsePayload): + return not self.__eq__(other) + return NotImplemented diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/derive_key.py python-pykmip-0.10.0/kmip/core/messages/payloads/derive_key.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/derive_key.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/derive_key.py 2020-02-25 16:05:27.000000000 +0000 @@ -21,9 +21,10 @@ from kmip.core import objects from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class DeriveKeyRequestPayload(primitives.Struct): +class DeriveKeyRequestPayload(base.RequestPayload): """ A request payload for the DeriveKey operation. @@ -68,9 +69,7 @@ cryptographic object. Optional, defaults to None. Required for read/write. """ - super(DeriveKeyRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(DeriveKeyRequestPayload, self).__init__() self._object_type = None self._unique_identifiers = None @@ -434,7 +433,7 @@ }) -class DeriveKeyResponsePayload(primitives.Struct): +class DeriveKeyResponsePayload(base.ResponsePayload): """ A response payload for the DeriveKey operation. @@ -461,9 +460,7 @@ newly derived cryptographic object. Optional, defaults to None. """ - super(DeriveKeyResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(DeriveKeyResponsePayload, self).__init__() self._unique_identifier = None self._template_attribute = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/destroy.py python-pykmip-0.10.0/kmip/core/messages/payloads/destroy.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/destroy.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/destroy.py 2020-02-25 16:05:27.000000000 +0000 @@ -16,18 +16,16 @@ from kmip.core import attributes from kmip.core import enums from kmip.core.enums import Tags - -from kmip.core.primitives import Struct - +from kmip.core.messages.payloads import base from kmip.core.utils import BytearrayStream # 4.21 -class DestroyRequestPayload(Struct): +class DestroyRequestPayload(base.RequestPayload): def __init__(self, unique_identifier=None): - super(DestroyRequestPayload, self).__init__(enums.Tags.REQUEST_PAYLOAD) + super(DestroyRequestPayload, self).__init__() self.unique_identifier = unique_identifier self.validate() @@ -67,12 +65,11 @@ pass -class DestroyResponsePayload(Struct): +class DestroyResponsePayload(base.ResponsePayload): def __init__(self, unique_identifier=None): - super(DestroyResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD) + super(DestroyResponsePayload, self).__init__() self.unique_identifier = unique_identifier self.validate() diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/discover_versions.py python-pykmip-0.10.0/kmip/core/messages/payloads/discover_versions.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/discover_versions.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/discover_versions.py 2020-02-25 16:05:27.000000000 +0000 @@ -16,20 +16,15 @@ from six.moves import xrange from kmip.core import enums - from kmip.core.messages.contents import ProtocolVersion - -from kmip.core.primitives import Struct - +from kmip.core.messages.payloads import base from kmip.core.utils import BytearrayStream -class DiscoverVersionsRequestPayload(Struct): +class DiscoverVersionsRequestPayload(base.RequestPayload): def __init__(self, protocol_versions=None): - super(DiscoverVersionsRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(DiscoverVersionsRequestPayload, self).__init__() if protocol_versions is None: self.protocol_versions = list() @@ -85,12 +80,10 @@ raise TypeError(msg) -class DiscoverVersionsResponsePayload(Struct): +class DiscoverVersionsResponsePayload(base.ResponsePayload): def __init__(self, protocol_versions=None): - super(DiscoverVersionsResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(DiscoverVersionsResponsePayload, self).__init__() if protocol_versions is None: self.protocol_versions = list() diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/encrypt.py python-pykmip-0.10.0/kmip/core/messages/payloads/encrypt.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/encrypt.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/encrypt.py 2020-02-25 16:05:27.000000000 +0000 @@ -19,9 +19,10 @@ from kmip.core import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class EncryptRequestPayload(primitives.Struct): +class EncryptRequestPayload(base.RequestPayload): """ A request payload for the Encrypt operation. @@ -33,13 +34,16 @@ data: The data to be encrypted in the form of a binary string. iv_counter_nonce: An IV/counter/nonce to be used with the encryption algorithm. Comes in the form of a binary string. + auth_additional_data: Any additional data to be authenticated via the + Authenticated Encryption Tag. Added in KMIP 1.4. """ def __init__(self, unique_identifier=None, cryptographic_parameters=None, data=None, - iv_counter_nonce=None): + iv_counter_nonce=None, + auth_additional_data=None): """ Construct an Encrypt request payload struct. @@ -56,20 +60,23 @@ encoding and decoding. iv_counter_nonce (bytes): The IV/counter/nonce value to be used with the encryption algorithm. Optional, defaults to None. + auth_additional_data (bytes): Any additional data to be + authenticated via the Authenticated Encryption Tag. + Added in KMIP 1.4. """ - super(EncryptRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(EncryptRequestPayload, self).__init__() self._unique_identifier = None self._cryptographic_parameters = None self._data = None self._iv_counter_nonce = None + self._auth_additional_data = None self.unique_identifier = unique_identifier self.cryptographic_parameters = cryptographic_parameters self.data = data self.iv_counter_nonce = iv_counter_nonce + self.auth_additional_data = auth_additional_data @property def unique_identifier(self): @@ -144,6 +151,25 @@ else: raise TypeError("IV/counter/nonce must be bytes") + @property + def auth_additional_data(self): + if self._auth_additional_data: + return self._auth_additional_data.value + else: + return None + + @auth_additional_data.setter + def auth_additional_data(self, value): + if value is None: + self._auth_additional_data = None + elif isinstance(value, six.binary_type): + self._auth_additional_data = primitives.ByteString( + value=value, + tag=enums.Tags.AUTHENTICATED_ENCRYPTION_ADDITIONAL_DATA + ) + else: + raise TypeError("authenticated additional data must be bytes") + def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0): """ Read the data encoding the Encrypt request payload and decode it @@ -202,6 +228,19 @@ kmip_version=kmip_version ) + if kmip_version >= enums.KMIPVersion.KMIP_1_4: + if self.is_tag_next( + enums.Tags.AUTHENTICATED_ENCRYPTION_ADDITIONAL_DATA, + local_stream + ): + self._auth_additional_data = primitives.ByteString( + tag=enums.Tags.AUTHENTICATED_ENCRYPTION_ADDITIONAL_DATA + ) + self._auth_additional_data.read( + local_stream, + kmip_version=kmip_version + ) + self.is_oversized(local_stream) def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0): @@ -243,6 +282,13 @@ kmip_version=kmip_version ) + if kmip_version >= enums.KMIPVersion.KMIP_1_4: + if self._auth_additional_data: + self._auth_additional_data.write( + local_stream, + kmip_version=kmip_version + ) + self.length = local_stream.length() super(EncryptRequestPayload, self).write( output_stream, @@ -261,6 +307,8 @@ return False elif self.iv_counter_nonce != other.iv_counter_nonce: return False + elif self.auth_additional_data != other.auth_additional_data: + return False else: return True else: @@ -279,7 +327,8 @@ repr(self.cryptographic_parameters) ), "data={0}".format(self.data), - "iv_counter_nonce={0}".format(self.iv_counter_nonce) + "iv_counter_nonce={0}".format(self.iv_counter_nonce), + "auth_additional_data={0}".format(self.auth_additional_data) ]) return "EncryptRequestPayload({0})".format(args) @@ -288,11 +337,12 @@ 'unique_identifier': self.unique_identifier, 'cryptographic_parameters': self.cryptographic_parameters, 'data': self.data, - 'iv_counter_nonce': self.iv_counter_nonce + 'iv_counter_nonce': self.iv_counter_nonce, + 'auth_additional_data': self.auth_additional_data }) -class EncryptResponsePayload(primitives.Struct): +class EncryptResponsePayload(base.ResponsePayload): """ A response payload for the Encrypt operation. @@ -302,12 +352,18 @@ data: The encrypted data in the form of a binary string. iv_counter_nonce: The IV/counter/nonce used with the encryption algorithm. Comes in the form of a binary string. + auth_tag: Specifies the tag that will be needed to + authenticate the decrypted data. Only returned on completion + of the encryption of the last of the plaintext by an + authenticated encryption cipher. Optional, defaults to None. + Added in KMIP 1.4. """ def __init__(self, unique_identifier=None, data=None, - iv_counter_nonce=None): + iv_counter_nonce=None, + auth_tag=None): """ Construct an Encrypt response payload struct. @@ -321,18 +377,23 @@ the encryption algorithm if it was required and if this value was not originally specified by the client. Optional, defaults to None. + auth_tag (bytes): Specifies the tag that will be needed to + authenticate the decrypted data. Only returned on completion + of the encryption of the last of the plaintext by an + authenticated encryption cipher. Optional, defaults to None. + Added in KMIP 1.4. """ - super(EncryptResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(EncryptResponsePayload, self).__init__() self._unique_identifier = None self._data = None self._iv_counter_nonce = None + self._auth_tag = None self.unique_identifier = unique_identifier self.data = data self.iv_counter_nonce = iv_counter_nonce + self.auth_tag = auth_tag @property def unique_identifier(self): @@ -391,6 +452,25 @@ else: raise TypeError("IV/counter/nonce must be bytes") + @property + def auth_tag(self): + if self._auth_tag: + return self._auth_tag.value + else: + return None + + @auth_tag.setter + def auth_tag(self, value): + if value is None: + self._auth_tag = None + elif isinstance(value, six.binary_type): + self._auth_tag = primitives.ByteString( + value=value, + tag=enums.Tags.AUTHENTICATED_ENCRYPTION_TAG + ) + else: + raise TypeError("authenticated encryption tag must be bytes") + def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0): """ Read the data encoding the Encrypt response payload and decode it @@ -445,6 +525,18 @@ kmip_version=kmip_version ) + if kmip_version >= enums.KMIPVersion.KMIP_1_4: + if self.is_tag_next( + enums.Tags.AUTHENTICATED_ENCRYPTION_TAG, local_stream + ): + self._auth_tag = primitives.ByteString( + tag=enums.Tags.AUTHENTICATED_ENCRYPTION_TAG + ) + self._auth_tag.read( + local_stream, + kmip_version=kmip_version + ) + self.is_oversized(local_stream) def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0): @@ -486,6 +578,13 @@ kmip_version=kmip_version ) + if kmip_version >= enums.KMIPVersion.KMIP_1_4: + if self._auth_tag: + self._auth_tag.write( + local_stream, + kmip_version=kmip_version + ) + self.length = local_stream.length() super(EncryptResponsePayload, self).write( output_stream, @@ -501,6 +600,8 @@ return False elif self.iv_counter_nonce != other.iv_counter_nonce: return False + elif self.auth_tag != other.auth_tag: + return False else: return True else: @@ -516,7 +617,8 @@ args = ", ".join([ "unique_identifier='{0}'".format(self.unique_identifier), "data={0}".format(self.data), - "iv_counter_nonce={0}".format(self.iv_counter_nonce) + "iv_counter_nonce={0}".format(self.iv_counter_nonce), + "auth_tag={0}".format(self.auth_tag) ]) return "EncryptResponsePayload({0})".format(args) @@ -524,5 +626,6 @@ return str({ 'unique_identifier': self.unique_identifier, 'data': self.data, - 'iv_counter_nonce': self.iv_counter_nonce + 'iv_counter_nonce': self.iv_counter_nonce, + 'auth_tag': self.auth_tag }) diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/get_attribute_list.py python-pykmip-0.10.0/kmip/core/messages/payloads/get_attribute_list.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/get_attribute_list.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/get_attribute_list.py 2020-02-25 16:05:27.000000000 +0000 @@ -20,9 +20,10 @@ from kmip.core import objects from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class GetAttributeListRequestPayload(primitives.Struct): +class GetAttributeListRequestPayload(base.RequestPayload): """ A request payload for the GetAttributeList operation. @@ -44,8 +45,7 @@ which the retrieved attribute names should be associated. Optional, defaults to None. """ - super(GetAttributeListRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD) + super(GetAttributeListRequestPayload, self).__init__() self._unique_identifier = None @@ -153,7 +153,7 @@ return NotImplemented -class GetAttributeListResponsePayload(primitives.Struct): +class GetAttributeListResponsePayload(base.ResponsePayload): """ A response payload for the GetAttributeList operation. @@ -182,9 +182,7 @@ defaults to None. """ - super(GetAttributeListResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(GetAttributeListResponsePayload, self).__init__() self._unique_identifier = None self._attribute_names = list() diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/get_attributes.py python-pykmip-0.10.0/kmip/core/messages/payloads/get_attributes.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/get_attributes.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/get_attributes.py 2020-02-25 16:05:27.000000000 +0000 @@ -17,9 +17,10 @@ from kmip.core import objects from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class GetAttributesRequestPayload(primitives.Struct): +class GetAttributesRequestPayload(base.RequestPayload): """ A request payload for the GetAttributes operation. @@ -48,8 +49,7 @@ attributes associated with the managed object. Optional, defaults to None. """ - super(GetAttributesRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD) + super(GetAttributesRequestPayload, self).__init__() self._unique_identifier = None self._attribute_names = list() @@ -272,7 +272,7 @@ return NotImplemented -class GetAttributesResponsePayload(primitives.Struct): +class GetAttributesResponsePayload(base.ResponsePayload): """ A response payload for the GetAttributes operation. @@ -297,8 +297,7 @@ attributes (list): A list of attribute structures associated with the managed object. Optional, defaults to None. """ - super(GetAttributesResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD) + super(GetAttributesResponsePayload, self).__init__() self._unique_identifier = None self._attributes = list() diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/get.py python-pykmip-0.10.0/kmip/core/messages/payloads/get.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/get.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/get.py 2020-02-25 16:05:27.000000000 +0000 @@ -20,11 +20,11 @@ from kmip.core import primitives from kmip.core import secrets from kmip.core import utils - from kmip.core.factories import secrets as secret_factory +from kmip.core.messages.payloads import base -class GetRequestPayload(primitives.Struct): +class GetRequestPayload(base.RequestPayload): """ A request payload for the Get operation. @@ -62,9 +62,7 @@ information for wrapping the returned object. Optional, defaults to None. """ - super(GetRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(GetRequestPayload, self).__init__() self._unique_identifier = None self._key_format_type = None @@ -301,7 +299,7 @@ }) -class GetResponsePayload(primitives.Struct): +class GetResponsePayload(base.ResponsePayload): """ A response payload for the Get operation. @@ -330,9 +328,8 @@ Optional, defaults to None. Required for read/write. """ - super(GetResponsePayload, self).__init__( - tag=enums.Tags.RESPONSE_PAYLOAD - ) + super(GetResponsePayload, self).__init__() + self._object_type = None self._unique_identifier = None self._secret = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/get_usage_allocation.py python-pykmip-0.10.0/kmip/core/messages/payloads/get_usage_allocation.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/get_usage_allocation.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/get_usage_allocation.py 2020-02-25 16:05:27.000000000 +0000 @@ -18,9 +18,10 @@ from kmip import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class GetUsageAllocationRequestPayload(primitives.Struct): +class GetUsageAllocationRequestPayload(base.RequestPayload): """ A request payload for the GetUsageAllocation operation. @@ -42,9 +43,7 @@ usage_limits_count (int): The number of usage limits units that should be reserved for the object. Optional, defaults to None. """ - super(GetUsageAllocationRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(GetUsageAllocationRequestPayload, self).__init__() self._unique_identifier = None self._usage_limits_count = None @@ -199,7 +198,7 @@ }) -class GetUsageAllocationResponsePayload(primitives.Struct): +class GetUsageAllocationResponsePayload(base.ResponsePayload): """ A response payload for the GetUsageAllocation operation. @@ -215,9 +214,7 @@ unique_identifier (string): The ID of the managed object (e.g., a public key) that was allocated. Optional, defaults to None. """ - super(GetUsageAllocationResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(GetUsageAllocationResponsePayload, self).__init__() self._unique_identifier = None self.unique_identifier = unique_identifier diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/__init__.py python-pykmip-0.10.0/kmip/core/messages/payloads/__init__.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/__init__.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/__init__.py 2020-02-25 16:05:27.000000000 +0000 @@ -13,6 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. +# Import payload base classes +from kmip.core.messages.payloads.base import ( + RequestPayload, + ResponsePayload +) + +# Import payload subclasses from kmip.core.messages.payloads.activate import ( ActivateRequestPayload, ActivateResponsePayload @@ -41,6 +48,10 @@ DecryptRequestPayload, DecryptResponsePayload ) +from kmip.core.messages.payloads.delete_attribute import ( + DeleteAttributeRequestPayload, + DeleteAttributeResponsePayload +) from kmip.core.messages.payloads.derive_key import ( DeriveKeyRequestPayload, DeriveKeyResponsePayload @@ -81,6 +92,10 @@ MACRequestPayload, MACResponsePayload ) +from kmip.core.messages.payloads.modify_attribute import ( + ModifyAttributeRequestPayload, + ModifyAttributeResponsePayload +) from kmip.core.messages.payloads.obtain_lease import ( ObtainLeaseRequestPayload, ObtainLeaseResponsePayload @@ -112,6 +127,10 @@ RevokeRequestPayload, RevokeResponsePayload ) +from kmip.core.messages.payloads.set_attribute import ( + SetAttributeRequestPayload, + SetAttributeResponsePayload +) from kmip.core.messages.payloads.sign import ( SignRequestPayload, SignResponsePayload @@ -137,6 +156,8 @@ "CreateKeyPairResponsePayload", "DecryptRequestPayload", "DecryptResponsePayload", + "DeleteAttributeRequestPayload", + "DeleteAttributeResponsePayload", "DeriveKeyRequestPayload", "DeriveKeyResponsePayload", "DestroyRequestPayload", @@ -157,6 +178,8 @@ "LocateResponsePayload", "MACRequestPayload", "MACResponsePayload", + "ModifyAttributeRequestPayload", + "ModifyAttributeResponsePayload", "ObtainLeaseRequestPayload", "ObtainLeaseResponsePayload", "PollRequestPayload", @@ -170,8 +193,12 @@ "RekeyKeyPairResponsePayload", "RekeyRequestPayload", "RekeyResponsePayload", + "RequestPayload", + "ResponsePayload", "RevokeRequestPayload", "RevokeResponsePayload", + "SetAttributeRequestPayload", + "SetAttributeResponsePayload", "SignRequestPayload", "SignResponsePayload", "SignatureVerifyRequestPayload", diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/locate.py python-pykmip-0.10.0/kmip/core/messages/payloads/locate.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/locate.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/locate.py 2020-02-25 16:05:27.000000000 +0000 @@ -19,9 +19,10 @@ from kmip.core import objects from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class LocateRequestPayload(primitives.Struct): +class LocateRequestPayload(base.RequestPayload): """ A request payload for the Locate operation. @@ -64,7 +65,7 @@ objects. Optional, defaults to None. Required for read/write for KMIP 2.0+. """ - super(LocateRequestPayload, self).__init__(enums.Tags.REQUEST_PAYLOAD) + super(LocateRequestPayload, self).__init__() self._maximum_items = None self._offset_items = None @@ -368,7 +369,7 @@ return '{' + value + '}' -class LocateResponsePayload(primitives.Struct): +class LocateResponsePayload(base.ResponsePayload): """ A response payload for the Locate operation. @@ -391,8 +392,7 @@ unique_identifiers (list): A list of strings specifying the object identifiers for matching objects. Optional, defaults to None. """ - super(LocateResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD) + super(LocateResponsePayload, self).__init__() self._located_items = None self._unique_identifiers = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/mac.py python-pykmip-0.10.0/kmip/core/messages/payloads/mac.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/mac.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/mac.py 2020-02-25 16:05:27.000000000 +0000 @@ -16,24 +16,20 @@ from kmip.core import enums from kmip.core import exceptions from kmip.core.enums import Tags - +from kmip.core.messages.payloads import base from kmip.core.objects import Data, MACData - -from kmip.core.primitives import Struct - from kmip.core.utils import BytearrayStream # 4.33 -class MACRequestPayload(Struct): +class MACRequestPayload(base.RequestPayload): def __init__(self, unique_identifier=None, cryptographic_parameters=None, data=None): - super(MACRequestPayload, self).__init__( - tag=enums.Tags.REQUEST_PAYLOAD) + super(MACRequestPayload, self).__init__() self._unique_identifier = None self._cryptographic_parameters = None @@ -137,13 +133,12 @@ ostream.write(tstream.buffer) -class MACResponsePayload(Struct): +class MACResponsePayload(base.ResponsePayload): def __init__(self, unique_identifier=None, mac_data=None): - super(MACResponsePayload, self).__init__( - tag=enums.Tags.RESPONSE_PAYLOAD) + super(MACResponsePayload, self).__init__() self._unique_identifier = None self._mac_data = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/modify_attribute.py python-pykmip-0.10.0/kmip/core/messages/payloads/modify_attribute.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/modify_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/modify_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,512 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import six + +from kmip.core import enums +from kmip.core import exceptions +from kmip.core import objects +from kmip.core import primitives +from kmip.core import utils +from kmip.core.messages.payloads import base + + +class ModifyAttributeRequestPayload(base.RequestPayload): + """ + A request payload for the ModifyAttribute operation. + + Attributes: + unique_identifier: The unique ID of the object on which attribute + deletion should be performed. + attribute: The attribute value to be modified by index. Used in KMIP + 1.0 - 1.4. + current_attribute: The current attribute to be modified on the + specified object. Used in KMIP 2.0+. + new_attribute: The new attribute value to set on the specified object. + Used in KMIP 2.0+. + """ + + def __init__(self, + unique_identifier=None, + attribute=None, + current_attribute=None, + new_attribute=None): + """ + Construct a ModifyAttribute request payload. + + Args: + unique_identifier (string): The unique ID of the object on which + attribute modification should be performed. Optional, defaults + to None. + attribute (struct): An Attribute object containing the attribute + name and index identifying the existing attribute, and the new + attribute value to replace the original attribute. Optional, + defaults to None. Used in KMIP 1.0 - 1.4. Required for + read/write. + current_attribute (struct): A CurrentAttribute object containing + the original attribute value on the specified object. Optional, + defaults to None. Used in KMIP 2.0+. + new_attribute (struct): A NewAttribute object containing the new + attribute value to set on the specified object. Optional, + defaults to None. Used in KMIP 2.0+. Required for read/write. + """ + super(ModifyAttributeRequestPayload, self).__init__() + + self._unique_identifier = None + self._attribute = None + self._current_attribute = None + self._new_attribute = None + + self.unique_identifier = unique_identifier + self.attribute = attribute + self.current_attribute = current_attribute + self.new_attribute = new_attribute + + @property + def unique_identifier(self): + if self._unique_identifier: + return self._unique_identifier.value + return None + + @unique_identifier.setter + def unique_identifier(self, value): + if value is None: + self._unique_identifier = None + elif isinstance(value, six.string_types): + self._unique_identifier = primitives.TextString( + value=value, + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + else: + raise TypeError("The unique identifier must be a string.") + + @property + def attribute(self): + if self._attribute: + return self._attribute + return None + + @attribute.setter + def attribute(self, value): + if value is None: + self._attribute = None + elif isinstance(value, objects.Attribute): + self._attribute = value + else: + raise TypeError("The attribute must be an Attribute object.") + + @property + def current_attribute(self): + if self._current_attribute: + return self._current_attribute + return None + + @current_attribute.setter + def current_attribute(self, value): + if value is None: + self._current_attribute = None + elif isinstance(value, objects.CurrentAttribute): + self._current_attribute = value + else: + raise TypeError( + "The current attribute must be a CurrentAttribute object." + ) + + @property + def new_attribute(self): + if self._new_attribute: + return self._new_attribute + return None + + @new_attribute.setter + def new_attribute(self, value): + if value is None: + self._new_attribute = None + elif isinstance(value, objects.NewAttribute): + self._new_attribute = value + else: + raise TypeError( + "The new attribute must be a NewAttribute object." + ) + + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Read the data encoding the ModifyAttribute request payload and decode + it into its constituent part. + + Args: + input_buffer (stream): A data stream containing encoded object + data, supporting a read method; usually a BytearrayStream + object. + kmip_version (KMIPVersion): An enumeration defining the KMIP + version with which the object will be decoded. Optional, + defaults to KMIP 1.0. + + Raises: + InvalidKmipEncoding: Raised if fields are missing from the + encoding. + """ + super(ModifyAttributeRequestPayload, self).read( + input_buffer, + kmip_version=kmip_version + ) + local_buffer = utils.BytearrayStream(input_buffer.read(self.length)) + + if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_buffer): + self._unique_identifier = primitives.TextString( + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + self._unique_identifier.read( + local_buffer, + kmip_version=kmip_version + ) + else: + self._unique_identifier = None + + if kmip_version < enums.KMIPVersion.KMIP_2_0: + if self.is_tag_next(enums.Tags.ATTRIBUTE, local_buffer): + self._attribute = objects.Attribute() + self._attribute.read(local_buffer, kmip_version=kmip_version) + else: + raise exceptions.InvalidKmipEncoding( + "The ModifyAttribute request payload encoding is missing " + "the attribute field." + ) + else: + if self.is_tag_next(enums.Tags.CURRENT_ATTRIBUTE, local_buffer): + self._current_attribute = objects.CurrentAttribute() + self._current_attribute.read( + local_buffer, + kmip_version=kmip_version + ) + else: + self._current_attribute = None + + if self.is_tag_next(enums.Tags.NEW_ATTRIBUTE, local_buffer): + self._new_attribute = objects.NewAttribute() + self._new_attribute.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The ModifyAttribute request payload encoding is missing " + "the new attribute field." + ) + + self.is_oversized(local_buffer) + + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Write the data encoding the ModifyAttribute request payload to a + stream. + + Args: + output_buffer (stream): A data stream in which to encode object + data, supporting a write method; usually a BytearrayStream + object. + kmip_version (KMIPVersion): An enumeration defining the KMIP + version with which the object will be encoded. Optional, + defaults to KMIP 1.0. + + Raises: + InvalidField: Raised if a required field is missing from the + payload object. + """ + local_buffer = utils.BytearrayStream() + + if self._unique_identifier: + self._unique_identifier.write( + local_buffer, + kmip_version=kmip_version + ) + + if kmip_version < enums.KMIPVersion.KMIP_2_0: + if self._attribute: + self._attribute.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The ModifyAttribute request payload is missing the " + "attribute field." + ) + else: + if self._current_attribute: + self._current_attribute.write( + local_buffer, + kmip_version=kmip_version + ) + + if self._new_attribute: + self._new_attribute.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The ModifyAttribute request payload is missing the new " + "attribute field." + ) + + self.length = local_buffer.length() + super(ModifyAttributeRequestPayload, self).write( + output_buffer, + kmip_version=kmip_version + ) + output_buffer.write(local_buffer.buffer) + + def __repr__(self): + args = [ + "unique_identifier='{}'".format(self.unique_identifier), + "attribute={}".format( + repr(self.attribute) if self.attribute else None + ), + "current_attribute={}".format( + repr(self.current_attribute) if self.current_attribute + else None + ), + "new_attribute={}".format( + repr(self.new_attribute) if self.new_attribute else None + ) + ] + return "ModifyAttributeRequestPayload({})".format(", ".join(args)) + + def __str__(self): + return str( + { + "unique_identifier": self.unique_identifier, + "attribute": str(self.attribute) if self.attribute else None, + "current_attribute": str( + self.current_attribute + ) if self.current_attribute else None, + "new_attribute": str( + self.new_attribute + ) if self.new_attribute else None + } + ) + + def __eq__(self, other): + if isinstance(other, ModifyAttributeRequestPayload): + if self.unique_identifier != other.unique_identifier: + return False + elif self.attribute != other.attribute: + return False + elif self.current_attribute != other.current_attribute: + return False + elif self.new_attribute != other.new_attribute: + return False + else: + return True + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, ModifyAttributeRequestPayload): + return not self.__eq__(other) + else: + return NotImplemented + + +class ModifyAttributeResponsePayload(base.ResponsePayload): + """ + A response payload for the ModifyAttribute operation. + + Attributes: + unique_identifier: The unique ID of the object on which the attribute + was set. + attribute: The newly modified attribute. Used in KMIP 1.0 - 1.4. + """ + + def __init__(self, unique_identifier=None, attribute=None): + """ + Construct a ModifyAttribute response payload. + + Args: + unique_identifier (string): The unique ID of the object on + which the attribute was set. Defaults to None. Required for + read/write. + attribute (struct): An Attribute object representing the newly + modified attribute. Optional, defaults to None. Used in KMIP + 1.0 - 1.4. Required for read/write. + """ + super(ModifyAttributeResponsePayload, self).__init__() + + self._unique_identifier = None + self._attribute = None + + self.unique_identifier = unique_identifier + self.attribute = attribute + + @property + def unique_identifier(self): + if self._unique_identifier: + return self._unique_identifier.value + return None + + @unique_identifier.setter + def unique_identifier(self, value): + if value is None: + self._unique_identifier = None + elif isinstance(value, six.string_types): + self._unique_identifier = primitives.TextString( + value=value, + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + else: + raise TypeError("The unique identifier must be a string.") + + @property + def attribute(self): + if self._attribute: + return self._attribute + return None + + @attribute.setter + def attribute(self, value): + if value is None: + self._attribute = None + elif isinstance(value, objects.Attribute): + self._attribute = value + else: + raise TypeError("The attribute must be an Attribute object.") + + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Read the data encoding the ModifyAttribute response payload and decode + it into its constituent parts. + + Args: + input_buffer (stream): A data stream containing encoded object + data, supporting a read method; usually a BytearrayStream + object. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be decoded. Optional, + defaults to KMIP 1.0. + + Raises: + InvalidKmipEncoding: Raised if any required fields are missing + from the encoding. + """ + super(ModifyAttributeResponsePayload, self).read( + input_buffer, + kmip_version=kmip_version + ) + local_buffer = utils.BytearrayStream(input_buffer.read(self.length)) + + if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_buffer): + self._unique_identifier = primitives.TextString( + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + self._unique_identifier.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The ModifyAttribute response payload encoding is missing the " + "unique identifier field." + ) + + if kmip_version < enums.KMIPVersion.KMIP_2_0: + if self.is_tag_next(enums.Tags.ATTRIBUTE, local_buffer): + self._attribute = objects.Attribute() + self._attribute.read(local_buffer, kmip_version=kmip_version) + else: + raise exceptions.InvalidKmipEncoding( + "The ModifyAttribute response payload encoding is missing " + "the attribute field." + ) + + self.is_oversized(local_buffer) + + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Write the data encoding the ModifyAttribute response payload to a + buffer. + + Args: + output_buffer (buffer): A data buffer in which to encode object + data, supporting a write method; usually a BytearrayStream + object. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be encoded. Optional, + defaults to KMIP 1.0. + + Raises: + InvalidField: Raised if a required field is missing from the + payload object. + """ + local_buffer = utils.BytearrayStream() + + if self._unique_identifier: + self._unique_identifier.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The ModifyAttribute response payload is missing the unique " + "identifier field." + ) + + if kmip_version < enums.KMIPVersion.KMIP_2_0: + if self._attribute: + self._attribute.write(local_buffer, kmip_version=kmip_version) + else: + raise exceptions.InvalidField( + "The ModifyAttribute response payload is missing the " + "attribute field." + ) + + self.length = local_buffer.length() + super(ModifyAttributeResponsePayload, self).write( + output_buffer, + kmip_version=kmip_version + ) + output_buffer.write(local_buffer.buffer) + + def __repr__(self): + args = [ + "unique_identifier='{}'".format(self.unique_identifier), + "attribute={}".format( + repr(self.attribute) if self.attribute else None + ) + ] + return "ModifyAttributeResponsePayload({})".format(", ".join(args)) + + def __str__(self): + return str( + { + "unique_identifier": self.unique_identifier, + "attribute": str(self.attribute) if self.attribute else None + } + ) + + def __eq__(self, other): + if isinstance(other, ModifyAttributeResponsePayload): + if self.unique_identifier != other.unique_identifier: + return False + elif self.attribute != other.attribute: + return False + else: + return True + return NotImplemented + + def __ne__(self, other): + if isinstance(other, ModifyAttributeResponsePayload): + return not self.__eq__(other) + return NotImplemented diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/obtain_lease.py python-pykmip-0.10.0/kmip/core/messages/payloads/obtain_lease.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/obtain_lease.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/obtain_lease.py 2020-02-25 16:05:27.000000000 +0000 @@ -18,9 +18,10 @@ from kmip import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class ObtainLeaseRequestPayload(primitives.Struct): +class ObtainLeaseRequestPayload(base.RequestPayload): """ A request payload for the ObtainLease operation. @@ -37,9 +38,7 @@ a public key) to obtain a lease for. Optional, defaults to None. """ - super(ObtainLeaseRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(ObtainLeaseRequestPayload, self).__init__() self._unique_identifier = None self.unique_identifier = unique_identifier @@ -152,7 +151,7 @@ }) -class ObtainLeaseResponsePayload(primitives.Struct): +class ObtainLeaseResponsePayload(base.ResponsePayload): """ A response payload for the ObtainLease operation. @@ -182,9 +181,7 @@ when the last change was made to the object or one of its attributes. Optional, defaults to None. """ - super(ObtainLeaseResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(ObtainLeaseResponsePayload, self).__init__() self._unique_identifier = None self._lease_time = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/poll.py python-pykmip-0.10.0/kmip/core/messages/payloads/poll.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/poll.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/poll.py 2020-02-25 16:05:27.000000000 +0000 @@ -18,9 +18,10 @@ from kmip import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class PollRequestPayload(primitives.Struct): +class PollRequestPayload(base.RequestPayload): """ A request payload for the Poll operation. @@ -38,9 +39,7 @@ operation to poll the status of, in bytes. Optional, defaults to None. """ - super(PollRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(PollRequestPayload, self).__init__() self._asynchronous_correlation_value = None self.asynchronous_correlation_value = asynchronous_correlation_value diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/query.py python-pykmip-0.10.0/kmip/core/messages/payloads/query.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/query.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/query.py 2020-02-25 16:05:27.000000000 +0000 @@ -21,9 +21,10 @@ from kmip.core import objects from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class QueryRequestPayload(primitives.Struct): +class QueryRequestPayload(base.RequestPayload): """ A request payload for the Query operation. @@ -41,7 +42,7 @@ Args: query_functions (list): A list of QueryFunction enumerations. """ - super(QueryRequestPayload, self).__init__(enums.Tags.REQUEST_PAYLOAD) + super(QueryRequestPayload, self).__init__() self._query_functions = None @@ -187,7 +188,7 @@ return NotImplemented -class QueryResponsePayload(primitives.Struct): +class QueryResponsePayload(base.ResponsePayload): """ A response payload for the Query operation. @@ -290,10 +291,7 @@ the storage protections supported by the server. Optional, defaults to None. Added in KMIP 2.0. """ - super(QueryResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) - + super(QueryResponsePayload, self).__init__() self._operations = None self._object_types = None self._vendor_identification = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/recover.py python-pykmip-0.10.0/kmip/core/messages/payloads/recover.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/recover.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/recover.py 2020-02-25 16:05:27.000000000 +0000 @@ -18,9 +18,10 @@ from kmip import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class RecoverRequestPayload(primitives.Struct): +class RecoverRequestPayload(base.RequestPayload): """ A request payload for the Recover operation. @@ -36,9 +37,7 @@ unique_identifier (string): The ID of the managed object (e.g., a public key) to recover. Optional, defaults to None. """ - super(RecoverRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(RecoverRequestPayload, self).__init__() self._unique_identifier = None self.unique_identifier = unique_identifier @@ -151,7 +150,7 @@ }) -class RecoverResponsePayload(primitives.Struct): +class RecoverResponsePayload(base.ResponsePayload): """ A response payload for the Recover operation. @@ -167,9 +166,7 @@ unique_identifier (string): The ID of the managed object (e.g., a public key) that was recovered. Optional, defaults to None. """ - super(RecoverResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(RecoverResponsePayload, self).__init__() self._unique_identifier = None self.unique_identifier = unique_identifier diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/register.py python-pykmip-0.10.0/kmip/core/messages/payloads/register.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/register.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/register.py 2020-02-25 16:05:27.000000000 +0000 @@ -21,11 +21,11 @@ from kmip.core import primitives from kmip.core import secrets from kmip.core import utils - from kmip.core.factories import secrets as secret_factory +from kmip.core.messages.payloads import base -class RegisterRequestPayload(primitives.Struct): +class RegisterRequestPayload(base.RequestPayload): """ A request payload for the Register operation. @@ -69,9 +69,7 @@ object. Added in KMIP 2.0. Optional, defaults to None. """ - super(RegisterRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(RegisterRequestPayload, self).__init__() self.secret_factory = secret_factory.SecretFactory() @@ -388,7 +386,7 @@ return '{' + value + '}' -class RegisterResponsePayload(primitives.Struct): +class RegisterResponsePayload(base.ResponsePayload): """ A response payload for the Register operation. @@ -411,9 +409,7 @@ structure containing a set of attributes that were set on the new object. Optional, defaults to None. """ - super(RegisterResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(RegisterResponsePayload, self).__init__() self._unique_identifier = None self._template_attribute = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/rekey_key_pair.py python-pykmip-0.10.0/kmip/core/messages/payloads/rekey_key_pair.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/rekey_key_pair.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/rekey_key_pair.py 2020-02-25 16:05:27.000000000 +0000 @@ -18,13 +18,13 @@ from kmip.core import objects from kmip.core import enums +from kmip.core.messages.payloads import base from kmip.core.messages.payloads.create_key_pair import \ CreateKeyPairResponsePayload -from kmip.core.primitives import Struct from kmip.core.utils import BytearrayStream -class RekeyKeyPairRequestPayload(Struct): +class RekeyKeyPairRequestPayload(base.RequestPayload): def __init__(self, private_key_uuid=None, @@ -32,9 +32,7 @@ common_template_attribute=None, private_key_template_attribute=None, public_key_template_attribute=None): - super(RekeyKeyPairRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(RekeyKeyPairRequestPayload, self).__init__() self.private_key_uuid = private_key_uuid self.offset = offset diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/rekey.py python-pykmip-0.10.0/kmip/core/messages/payloads/rekey.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/rekey.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/rekey.py 2020-02-25 16:05:27.000000000 +0000 @@ -16,9 +16,10 @@ from kmip.core import objects from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class RekeyRequestPayload(primitives.Struct): +class RekeyRequestPayload(base.RequestPayload): """ A request payload for the Rekey operation. @@ -47,9 +48,7 @@ cryptographic length) that should be set on the replacement key. Optional, defaults to None. """ - super(RekeyRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(RekeyRequestPayload, self).__init__() self._unique_identifier = None self._offset = None @@ -227,7 +226,7 @@ }) -class RekeyResponsePayload(primitives.Struct): +class RekeyResponsePayload(base.ResponsePayload): """ A response payload for the Rekey operation. @@ -250,9 +249,7 @@ cryptographic length) that were set by the server on the replacement key. Optional, defaults to None. """ - super(RekeyResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(RekeyResponsePayload, self).__init__() self._unique_identifier = None self._template_attribute = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/revoke.py python-pykmip-0.10.0/kmip/core/messages/payloads/revoke.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/revoke.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/revoke.py 2020-02-25 16:05:27.000000000 +0000 @@ -17,13 +17,11 @@ from kmip.core import enums from kmip.core import objects from kmip.core import primitives - -from kmip.core.primitives import Struct - +from kmip.core.messages.payloads import base from kmip.core.utils import BytearrayStream -class RevokeRequestPayload(Struct): +class RevokeRequestPayload(base.RequestPayload): """ A request payload for the Revoke operation. @@ -51,8 +49,7 @@ compromise_occurrence_date (DateTime): the datetime when the object was first believed to be compromised. """ - super(RevokeRequestPayload, self).__init__( - tag=enums.Tags.REQUEST_PAYLOAD) + super(RevokeRequestPayload, self).__init__() self.unique_identifier = unique_identifier self.compromise_occurrence_date = compromise_occurrence_date self.revocation_reason = revocation_reason @@ -145,7 +142,7 @@ raise TypeError(msg) -class RevokeResponsePayload(Struct): +class RevokeResponsePayload(base.ResponsePayload): """ A response payload for the Revoke operation. The payload contains the server response to the initial Revoke request. @@ -161,8 +158,7 @@ unique_identifier (UniqueIdentifier): The UUID of a managed cryptographic object. """ - super(RevokeResponsePayload, self).__init__( - tag=enums.Tags.RESPONSE_PAYLOAD) + super(RevokeResponsePayload, self).__init__() if unique_identifier is None: self.unique_identifier = attributes.UniqueIdentifier() else: diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/set_attribute.py python-pykmip-0.10.0/kmip/core/messages/payloads/set_attribute.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/set_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/set_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,399 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import six + +from kmip.core import enums +from kmip.core import exceptions +from kmip.core import objects +from kmip.core import primitives +from kmip.core import utils +from kmip.core.messages.payloads import base + + +class SetAttributeRequestPayload(base.RequestPayload): + """ + A request payload for the SetAttribute operation. + + Attributes: + unique_identifier: The unique ID of the object on which attribute + deletion should be performed. + new_attribute: The attribute to set on the specified object. + """ + + def __init__(self, + unique_identifier=None, + new_attribute=None): + """ + Construct a SetAttribute request payload. + + Args: + unique_identifier (string): The unique ID of the object on which + the attribute should be set. Optional, defaults to + None. + new_attribute (struct): A NewAttribute object containing the new + attribute value to set on the specified object. Optional, + defaults to None. Required for read/write. + """ + super(SetAttributeRequestPayload, self).__init__() + + self._unique_identifier = None + self._new_attribute = None + + self.unique_identifier = unique_identifier + self.new_attribute = new_attribute + + @property + def unique_identifier(self): + if self._unique_identifier: + return self._unique_identifier.value + return None + + @unique_identifier.setter + def unique_identifier(self, value): + if value is None: + self._unique_identifier = None + elif isinstance(value, six.string_types): + self._unique_identifier = primitives.TextString( + value=value, + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + else: + raise TypeError("The unique identifier must be a string.") + + @property + def new_attribute(self): + if self._new_attribute: + return self._new_attribute + return None + + @new_attribute.setter + def new_attribute(self, value): + if value is None: + self._new_attribute = None + elif isinstance(value, objects.NewAttribute): + self._new_attribute = value + else: + raise TypeError( + "The new attribute must be a NewAttribute object." + ) + + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_2_0): + """ + Read the data encoding the SetAttribute request payload and decode + it into its constituent part. + + Args: + input_buffer (stream): A data stream containing encoded object + data, supporting a read method; usually a BytearrayStream + object. + kmip_version (KMIPVersion): An enumeration defining the KMIP + version with which the object will be decoded. Optional, + defaults to KMIP 1.0. + + Raises: + VersionNotSupported: Raised when a KMIP version is provided that + does not support the SetAttribute operation. + InvalidKmipEncoding: Raised if fields are missing from the + encoding. + """ + if kmip_version < enums.KMIPVersion.KMIP_2_0: + raise exceptions.VersionNotSupported( + "KMIP {} does not support the SetAttribute operation.".format( + kmip_version.value + ) + ) + + super(SetAttributeRequestPayload, self).read( + input_buffer, + kmip_version=kmip_version + ) + local_buffer = utils.BytearrayStream(input_buffer.read(self.length)) + + if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_buffer): + self._unique_identifier = primitives.TextString( + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + self._unique_identifier.read( + local_buffer, + kmip_version=kmip_version + ) + else: + self._unique_identifier = None + + if self.is_tag_next(enums.Tags.NEW_ATTRIBUTE, local_buffer): + self._new_attribute = objects.NewAttribute() + self._new_attribute.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The SetAttribute request payload encoding is missing the new " + "attribute field." + ) + + self.is_oversized(local_buffer) + + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_2_0): + """ + Write the data encoding the SetAttribute request payload to a + stream. + + Args: + output_buffer (stream): A data stream in which to encode object + data, supporting a write method; usually a BytearrayStream + object. + kmip_version (KMIPVersion): An enumeration defining the KMIP + version with which the object will be encoded. Optional, + defaults to KMIP 1.0. + + Raises: + VersionNotSupported: Raised when a KMIP version is provided that + does not support the SetAttribute operation. + InvalidField: Raised if a required field is missing from the + payload object. + """ + if kmip_version < enums.KMIPVersion.KMIP_2_0: + raise exceptions.VersionNotSupported( + "KMIP {} does not support the SetAttribute operation.".format( + kmip_version.value + ) + ) + + local_buffer = utils.BytearrayStream() + + if self._unique_identifier: + self._unique_identifier.write( + local_buffer, + kmip_version=kmip_version + ) + + if self._new_attribute: + self._new_attribute.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The SetAttribute request payload is missing the new " + "attribute field." + ) + + self.length = local_buffer.length() + super(SetAttributeRequestPayload, self).write( + output_buffer, + kmip_version=kmip_version + ) + output_buffer.write(local_buffer.buffer) + + def __repr__(self): + args = [ + "unique_identifier='{}'".format(self.unique_identifier), + "new_attribute={}".format( + repr(self.new_attribute) if self.new_attribute else None + ) + ] + return "SetAttributeRequestPayload({})".format(", ".join(args)) + + def __str__(self): + return str( + { + "unique_identifier": self.unique_identifier, + "new_attribute": str( + self.new_attribute + ) if self.new_attribute else None + } + ) + + def __eq__(self, other): + if isinstance(other, SetAttributeRequestPayload): + if self.unique_identifier != other.unique_identifier: + return False + elif self.new_attribute != other.new_attribute: + return False + else: + return True + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, SetAttributeRequestPayload): + return not self.__eq__(other) + else: + return NotImplemented + + +class SetAttributeResponsePayload(base.ResponsePayload): + """ + A response payload for the SetAttribute operation. + + Attributes: + unique_identifier: The unique ID of the object on which the attribute + was set. + """ + + def __init__(self, unique_identifier=None): + """ + Construct a SetAttribute response payload. + + Args: + unique_identifier (string): The unique ID of the object on + which the attribute was set. Defaults to None. Required for + read/write. + """ + super(SetAttributeResponsePayload, self).__init__() + + self._unique_identifier = None + + self.unique_identifier = unique_identifier + + @property + def unique_identifier(self): + if self._unique_identifier: + return self._unique_identifier.value + return None + + @unique_identifier.setter + def unique_identifier(self, value): + if value is None: + self._unique_identifier = None + elif isinstance(value, six.string_types): + self._unique_identifier = primitives.TextString( + value=value, + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + else: + raise TypeError("The unique identifier must be a string.") + + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_2_0): + """ + Read the data encoding the SetAttribute response payload and decode + it into its constituent parts. + + Args: + input_buffer (stream): A data stream containing encoded object + data, supporting a read method; usually a BytearrayStream + object. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be decoded. Optional, + defaults to KMIP 1.0. + + Raises: + VersionNotSupported: Raised when a KMIP version is provided that + does not support the SetAttribute operation. + InvalidKmipEncoding: Raised if any required fields are missing + from the encoding. + """ + if kmip_version < enums.KMIPVersion.KMIP_2_0: + raise exceptions.VersionNotSupported( + "KMIP {} does not support the SetAttribute operation.".format( + kmip_version.value + ) + ) + + super(SetAttributeResponsePayload, self).read( + input_buffer, + kmip_version=kmip_version + ) + local_buffer = utils.BytearrayStream(input_buffer.read(self.length)) + + if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_buffer): + self._unique_identifier = primitives.TextString( + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + self._unique_identifier.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The SetAttribute response payload encoding is missing the " + "unique identifier field." + ) + + self.is_oversized(local_buffer) + + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_2_0): + """ + Write the data encoding the SetAttribute response payload to a + buffer. + + Args: + output_buffer (buffer): A data buffer in which to encode object + data, supporting a write method; usually a BytearrayStream + object. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be encoded. Optional, + defaults to KMIP 1.0. + + Raises: + VersionNotSupported: Raised when a KMIP version is provided that + does not support the SetAttribute operation. + InvalidField: Raised if a required field is missing from the + payload object. + """ + if kmip_version < enums.KMIPVersion.KMIP_2_0: + raise exceptions.VersionNotSupported( + "KMIP {} does not support the SetAttribute operation.".format( + kmip_version.value + ) + ) + + local_buffer = utils.BytearrayStream() + + if self._unique_identifier: + self._unique_identifier.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The SetAttribute response payload is missing the unique " + "identifier field." + ) + + self.length = local_buffer.length() + super(SetAttributeResponsePayload, self).write( + output_buffer, + kmip_version=kmip_version + ) + output_buffer.write(local_buffer.buffer) + + def __repr__(self): + args = [ + "unique_identifier='{}'".format(self.unique_identifier) + ] + return "SetAttributeResponsePayload({})".format(", ".join(args)) + + def __str__(self): + return str( + { + "unique_identifier": self.unique_identifier + } + ) + + def __eq__(self, other): + if isinstance(other, SetAttributeResponsePayload): + if self.unique_identifier != other.unique_identifier: + return False + else: + return True + return NotImplemented + + def __ne__(self, other): + if isinstance(other, SetAttributeResponsePayload): + return not self.__eq__(other) + return NotImplemented diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/signature_verify.py python-pykmip-0.10.0/kmip/core/messages/payloads/signature_verify.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/signature_verify.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/signature_verify.py 2020-02-25 16:05:27.000000000 +0000 @@ -19,9 +19,10 @@ from kmip.core import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class SignatureVerifyRequestPayload(primitives.Struct): +class SignatureVerifyRequestPayload(base.RequestPayload): """ A request payload for the SignatureVerify operation. @@ -77,9 +78,7 @@ not the payload is the last in a series for a multi-payload operation. Optional, defaults to None. """ - super(SignatureVerifyRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(SignatureVerifyRequestPayload, self).__init__() self._unique_identifier = None self._cryptographic_parameters = None @@ -438,7 +437,7 @@ }) -class SignatureVerifyResponsePayload(primitives.Struct): +class SignatureVerifyResponsePayload(base.ResponsePayload): """ A response payload for the SignatureVerify operation. @@ -472,9 +471,7 @@ value, allowing the linking together of individual payloads for a single overarching operation. Optional, defaults to None. """ - super(SignatureVerifyResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(SignatureVerifyResponsePayload, self).__init__() self._unique_identifier = None self._validity_indicator = None diff -Nru python-pykmip-0.9.1/kmip/core/messages/payloads/sign.py python-pykmip-0.10.0/kmip/core/messages/payloads/sign.py --- python-pykmip-0.9.1/kmip/core/messages/payloads/sign.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/messages/payloads/sign.py 2020-02-25 16:05:27.000000000 +0000 @@ -19,9 +19,10 @@ from kmip.core import enums from kmip.core import primitives from kmip.core import utils +from kmip.core.messages.payloads import base -class SignRequestPayload(primitives.Struct): +class SignRequestPayload(base.RequestPayload): """ A request payload for the Sign operation. @@ -51,9 +52,7 @@ managed object will be used instead. data (bytes): The data to be signed, in binary form. """ - super(SignRequestPayload, self).__init__( - enums.Tags.REQUEST_PAYLOAD - ) + super(SignRequestPayload, self).__init__() self._unique_identifier = None self._cryptographic_parameters = None @@ -247,7 +246,7 @@ }) -class SignResponsePayload(primitives.Struct): +class SignResponsePayload(base.ResponsePayload): """ A response payload for the Sign operation. @@ -260,9 +259,7 @@ def __init__(self, unique_identifier=None, signature_data=None): - super(SignResponsePayload, self).__init__( - enums.Tags.RESPONSE_PAYLOAD - ) + super(SignResponsePayload, self).__init__() self._unique_identifier = None self._signature_data = None diff -Nru python-pykmip-0.9.1/kmip/core/objects.py python-pykmip-0.10.0/kmip/core/objects.py --- python-pykmip-0.9.1/kmip/core/objects.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/objects.py 2020-02-25 16:05:27.000000000 +0000 @@ -175,6 +175,368 @@ return NotImplemented +class CurrentAttribute(primitives.Struct): + """ + A structure containing a single attribute. + + This is intended for use with KMIP 2.0+. + + Attributes: + attribute: An attribute instance. + """ + + def __init__(self, attribute=None): + """ + Construct a CurrentAttribute structure. + + Args: + attribute (struct): An attribute structure of varying type. + Defaults to None. Required for read/write. + """ + super(CurrentAttribute, self).__init__( + tag=enums.Tags.CURRENT_ATTRIBUTE + ) + + self._factory = AttributeValueFactory() + + self._attribute = None + + self.attribute = attribute + + @property + def attribute(self): + if self._attribute: + return self._attribute + return None + + @attribute.setter + def attribute(self, value): + if value is None: + self._attribute = None + elif isinstance(value, primitives.Base): + if enums.is_attribute(value.tag): + self._attribute = value + else: + raise TypeError( + "The attribute must be a supported attribute type." + ) + else: + raise TypeError( + "The attribute must be a Base object, not a {}.".format( + type(value) + ) + ) + + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_2_0): + """ + Read the data stream and decode the CurrentAttribute structure into + its parts. + + Args: + input_buffer (stream): A data stream containing encoded object + data, supporting a read method. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be decoded. Optional, + defaults to KMIP 2.0. + + Raises: + AttributeNotSupported: Raised when an invalid value is decoded as + the attribute from the encoding. + InvalidKmipEncoding: Raised if the attribute is missing from the + encoding. + VersionNotSupported: Raised when a KMIP version is provided that + does not support the CurrentAttribute structure. + """ + if kmip_version < enums.KMIPVersion.KMIP_2_0: + raise exceptions.VersionNotSupported( + "KMIP {} does not support the CurrentAttribute object.".format( + kmip_version.value + ) + ) + + super(CurrentAttribute, self).read( + input_buffer, + kmip_version=kmip_version + ) + local_buffer = BytearrayStream(input_buffer.read(self.length)) + + if len(local_buffer) < 3: + raise exceptions.InvalidKmipEncoding( + "The CurrentAttribute encoding is missing the attribute field." + ) + tag = struct.unpack('!I', b'\x00' + local_buffer.peek(3))[0] + if enums.is_enum_value(enums.Tags, tag): + tag = enums.Tags(tag) + if enums.is_attribute(tag, kmip_version=kmip_version): + value = self._factory.create_attribute_value_by_enum(tag, None) + value.read(local_buffer, kmip_version=kmip_version) + self._attribute = value + else: + raise exceptions.AttributeNotSupported( + "Attribute {} is not supported by KMIP {}.".format( + tag.name, + kmip_version.value + ) + ) + else: + raise exceptions.InvalidKmipEncoding( + "The CurrentAttribute encoding is missing the attribute field." + ) + + self.is_oversized(local_buffer) + + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_2_0): + """ + Write the CurrentAttribute structure encoding to the data stream. + + Args: + output_buffer (stream): A data stream in which to encode + CurrentAttribute structure data, supporting a write method. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be encoded. Optional, + defaults to KMIP 2.0. + + Raises: + AttributeNotSupported: Raised if an unsupported attribute is + found while encoding. + InvalidField: Raised when the attribute is unspecified at write + time. + VersionNotSupported: Raised when a KMIP version is provided that + does not support the CurrentAttribute object. + """ + if kmip_version < enums.KMIPVersion.KMIP_2_0: + raise exceptions.VersionNotSupported( + "KMIP {} does not support the CurrentAttribute object.".format( + kmip_version.value + ) + ) + + local_buffer = BytearrayStream() + + if self._attribute: + tag = self._attribute.tag + if not enums.is_attribute(tag, kmip_version=kmip_version): + raise exceptions.AttributeNotSupported( + "Attribute {} is not supported by KMIP {}.".format( + tag.name, + kmip_version.value + ) + ) + self._attribute.write(local_buffer, kmip_version=kmip_version) + else: + raise exceptions.InvalidField( + "The CurrentAttribute object is missing the attribute field." + ) + + self.length = local_buffer.length() + super(CurrentAttribute, self).write( + output_buffer, + kmip_version=kmip_version + ) + output_buffer.write(local_buffer.buffer) + + def __repr__(self): + return "CurrentAttribute(attribute={})".format(repr(self.attribute)) + + def __str__(self): + value = '"attribute": {}'.format(repr(self.attribute)) + return '{' + value + '}' + + def __eq__(self, other): + if not isinstance(other, CurrentAttribute): + return NotImplemented + elif self.attribute != other.attribute: + return False + return True + + def __ne__(self, other): + if isinstance(other, CurrentAttribute): + return not (self == other) + else: + return NotImplemented + + +class NewAttribute(primitives.Struct): + """ + A structure containing a single attribute. + + This is intended for use with KMIP 2.0+. + + Attributes: + attribute: An attribute instance. + """ + + def __init__(self, attribute=None): + """ + Construct a NewAttribute structure. + + Args: + attribute (struct): An attribute structure of varying type. + Defaults to None. Required for read/write. + """ + super(NewAttribute, self).__init__( + tag=enums.Tags.NEW_ATTRIBUTE + ) + + self._factory = AttributeValueFactory() + + self._attribute = None + + self.attribute = attribute + + @property + def attribute(self): + if self._attribute: + return self._attribute + return None + + @attribute.setter + def attribute(self, value): + if value is None: + self._attribute = None + elif isinstance(value, primitives.Base): + if enums.is_attribute(value.tag): + self._attribute = value + else: + raise TypeError( + "The attribute must be a supported attribute type." + ) + else: + raise TypeError( + "The attribute must be a Base object, not a {}.".format( + type(value) + ) + ) + + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_2_0): + """ + Read the data stream and decode the NewAttribute structure into + its parts. + + Args: + input_buffer (stream): A data stream containing encoded object + data, supporting a read method. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be decoded. Optional, + defaults to KMIP 2.0. + + Raises: + AttributeNotSupported: Raised when an invalid value is decoded as + the attribute from the encoding. + InvalidKmipEncoding: Raised if the attribute is missing from the + encoding. + VersionNotSupported: Raised when a KMIP version is provided that + does not support the CurrentAttribute structure. + """ + if kmip_version < enums.KMIPVersion.KMIP_2_0: + raise exceptions.VersionNotSupported( + "KMIP {} does not support the NewAttribute object.".format( + kmip_version.value + ) + ) + + super(NewAttribute, self).read( + input_buffer, + kmip_version=kmip_version + ) + local_buffer = BytearrayStream(input_buffer.read(self.length)) + + if len(local_buffer) < 3: + raise exceptions.InvalidKmipEncoding( + "The NewAttribute encoding is missing the attribute field." + ) + tag = struct.unpack('!I', b'\x00' + local_buffer.peek(3))[0] + if enums.is_enum_value(enums.Tags, tag): + tag = enums.Tags(tag) + if enums.is_attribute(tag, kmip_version=kmip_version): + value = self._factory.create_attribute_value_by_enum(tag, None) + value.read(local_buffer, kmip_version=kmip_version) + self._attribute = value + else: + raise exceptions.AttributeNotSupported( + "Attribute {} is not supported by KMIP {}.".format( + tag.name, + kmip_version.value + ) + ) + else: + raise exceptions.InvalidKmipEncoding( + "The NewAttribute encoding is missing the attribute field." + ) + + self.is_oversized(local_buffer) + + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_2_0): + """ + Write the NewAttribute structure encoding to the data stream. + + Args: + output_buffer (stream): A data stream in which to encode + NewAttribute structure data, supporting a write method. + kmip_version (enum): A KMIPVersion enumeration defining the KMIP + version with which the object will be encoded. Optional, + defaults to KMIP 2.0. + + Raises: + AttributeNotSupported: Raised if an unsupported attribute is + found while encoding. + InvalidField: Raised when the attribute is unspecified at write + time. + VersionNotSupported: Raised when a KMIP version is provided that + does not support the NewAttribute object. + """ + if kmip_version < enums.KMIPVersion.KMIP_2_0: + raise exceptions.VersionNotSupported( + "KMIP {} does not support the NewAttribute object.".format( + kmip_version.value + ) + ) + + local_buffer = BytearrayStream() + + if self._attribute: + tag = self._attribute.tag + if not enums.is_attribute(tag, kmip_version=kmip_version): + raise exceptions.AttributeNotSupported( + "Attribute {} is not supported by KMIP {}.".format( + tag.name, + kmip_version.value + ) + ) + self._attribute.write(local_buffer, kmip_version=kmip_version) + else: + raise exceptions.InvalidField( + "The NewAttribute object is missing the attribute field." + ) + + self.length = local_buffer.length() + super(NewAttribute, self).write( + output_buffer, + kmip_version=kmip_version + ) + output_buffer.write(local_buffer.buffer) + + def __repr__(self): + return "NewAttribute(attribute={})".format(repr(self.attribute)) + + def __str__(self): + value = '"attribute": {}'.format(repr(self.attribute)) + return '{' + value + '}' + + def __eq__(self, other): + if not isinstance(other, NewAttribute): + return NotImplemented + elif self.attribute != other.attribute: + return False + return True + + def __ne__(self, other): + if isinstance(other, NewAttribute): + return not (self == other) + else: + return NotImplemented + + class AttributeReference(primitives.Struct): """ A structure containing reference information for an attribute. diff -Nru python-pykmip-0.9.1/kmip/core/policy.py python-pykmip-0.10.0/kmip/core/policy.py --- python-pykmip-0.9.1/kmip/core/policy.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/policy.py 2020-02-25 16:05:27.000000000 +0000 @@ -123,6 +123,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.ALLOW_OWNER, + enums.Operation.SET_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.OBTAIN_LEASE: enums.Policy.ALLOW_ALL, enums.Operation.ACTIVATE: enums.Policy.ALLOW_OWNER, enums.Operation.REVOKE: enums.Policy.ALLOW_OWNER, @@ -142,6 +143,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.ALLOW_OWNER, + enums.Operation.SET_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.OBTAIN_LEASE: enums.Policy.ALLOW_OWNER, enums.Operation.GET_USAGE_ALLOCATION: enums.Policy.ALLOW_OWNER, enums.Operation.ACTIVATE: enums.Policy.ALLOW_OWNER, @@ -159,6 +161,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.ALLOW_OWNER, + enums.Operation.SET_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.OBTAIN_LEASE: enums.Policy.ALLOW_ALL, enums.Operation.ACTIVATE: enums.Policy.ALLOW_OWNER, enums.Operation.REVOKE: enums.Policy.ALLOW_OWNER, @@ -178,6 +181,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.ALLOW_OWNER, + enums.Operation.SET_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.OBTAIN_LEASE: enums.Policy.ALLOW_OWNER, enums.Operation.GET_USAGE_ALLOCATION: enums.Policy.ALLOW_OWNER, enums.Operation.ACTIVATE: enums.Policy.ALLOW_OWNER, @@ -198,6 +202,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.ALLOW_OWNER, + enums.Operation.SET_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.OBTAIN_LEASE: enums.Policy.ALLOW_OWNER, enums.Operation.GET_USAGE_ALLOCATION: enums.Policy.ALLOW_OWNER, enums.Operation.ACTIVATE: enums.Policy.ALLOW_OWNER, @@ -214,6 +219,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.ALLOW_OWNER, + enums.Operation.SET_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DESTROY: enums.Policy.ALLOW_OWNER }, enums.ObjectType.SECRET_DATA: { @@ -228,6 +234,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.ALLOW_OWNER, + enums.Operation.SET_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.OBTAIN_LEASE: enums.Policy.ALLOW_OWNER, enums.Operation.GET_USAGE_ALLOCATION: enums.Policy.ALLOW_OWNER, enums.Operation.ACTIVATE: enums.Policy.ALLOW_OWNER, @@ -248,6 +255,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.ALLOW_OWNER, + enums.Operation.SET_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.OBTAIN_LEASE: enums.Policy.ALLOW_OWNER, enums.Operation.GET_USAGE_ALLOCATION: enums.Policy.ALLOW_OWNER, enums.Operation.ACTIVATE: enums.Policy.ALLOW_OWNER, @@ -268,6 +276,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.ALLOW_OWNER, + enums.Operation.SET_ATTRIBUTE: enums.Policy.ALLOW_OWNER, enums.Operation.OBTAIN_LEASE: enums.Policy.ALLOW_OWNER, enums.Operation.GET_USAGE_ALLOCATION: enums.Policy.ALLOW_OWNER, enums.Operation.ACTIVATE: enums.Policy.ALLOW_OWNER, @@ -288,6 +297,7 @@ enums.Operation.ADD_ATTRIBUTE: enums.Policy.DISALLOW_ALL, enums.Operation.MODIFY_ATTRIBUTE: enums.Policy.DISALLOW_ALL, enums.Operation.DELETE_ATTRIBUTE: enums.Policy.DISALLOW_ALL, + enums.Operation.SET_ATTRIBUTE: enums.Policy.DISALLOW_ALL, enums.Operation.DESTROY: enums.Policy.DISALLOW_ALL } } diff -Nru python-pykmip-0.9.1/kmip/core/secrets.py python-pykmip-0.10.0/kmip/core/secrets.py --- python-pykmip-0.9.1/kmip/core/secrets.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/core/secrets.py 2020-02-25 16:05:27.000000000 +0000 @@ -13,22 +13,26 @@ # License for the specific language governing permissions and limitations # under the License. +import six + from kmip.core.attributes import CertificateType from kmip.core import enums from kmip.core.enums import Tags +from kmip.core import exceptions from kmip.core.misc import CertificateValue +from kmip.core import objects from kmip.core.objects import Attribute from kmip.core.objects import KeyBlock +from kmip.core import primitives from kmip.core.primitives import Struct -from kmip.core.primitives import Integer from kmip.core.primitives import Enumeration -from kmip.core.primitives import BigInteger from kmip.core.primitives import ByteString +from kmip.core import utils from kmip.core.utils import BytearrayStream @@ -219,38 +223,25 @@ pass -# 2.2.5 -class SplitKey(Struct): - - class SplitKeyParts(Integer): - - def __init__(self, value=None): - super(SplitKey.SplitKeyParts, self).__init__( - value, Tags.SPLIT_KEY_PARTS) - - class KeyPartIdentifier(Integer): - - def __init__(self, value=None): - super(SplitKey.KeyPartIdentifier, self).__init__( - value, Tags.KEY_PART_IDENTIFIER) - - class SplitKeyThreshold(Integer): - - def __init__(self, value=None): - super(SplitKey.SplitKeyThreshold, self).__init__( - value, Tags.SPLIT_KEY_THRESHOLD) - - class SplitKeyMethod(Enumeration): - - def __init__(self, value=None): - super(SplitKey.SplitKeyMethod, self).__init__( - enums.SplitKeyMethod, value, Tags.SPLIT_KEY_METHOD) +class SplitKey(primitives.Struct): + """ + A split key cryptographic object. - class PrimeFieldSize(BigInteger): + This object represents a symmetric or private key that has been split into + multiple parts. The fields of this object specify how the key was split + and how it can be reassembled. - def __init__(self, value=None): - super(SplitKey.PrimeFieldSize, self).__init__( - value, Tags.PRIME_FIELD_SIZE) + Attributes: + split_key_parts: The total number of parts of the split key. + key_part_identifier: The ID specifying the part of the key in the key + block. + split_key_threshold: The minimum number of parts needed to reconstruct + the key. + split_key_method: The method by which the key was split. + prime_field_size: The prime field size used for the Polynomial Sharing + Prime Field split key method. + key_block: The split key part held by this object. + """ def __init__(self, split_key_parts=None, @@ -259,62 +250,374 @@ split_key_method=None, prime_field_size=None, key_block=None): - super(SplitKey, self).__init__(Tags.SPLIT_KEY) + """ + Construct a SplitKey object. + + Args: + split_key_parts (int): An integer specifying the total number of + parts of the split key. Optional, defaults to None. Required + for read/write. + key_part_identifier (int): An integer specifying which key part is + contained in the key block. Optional, defaults to None. + Required for read/write. + split_key_threshold (int): An integer specifying the minimum number + of key parts required to reconstruct the split key. Optional, + defaults to None. Required for read/write. + split_key_method (enum): A SplitKeyMethod enumeration specifying + the method by which the key was split. Optional, defaults to + None. Required for read/write. + prime_field_size (int): A big integer specifying the prime field + size used for the Polynomial Sharing Prime Field split key + method. Optional, defaults to None. Required for read/write + only if the split key method is Polynomial Sharing Prime Field. + key_block (struct): A KeyBlock structure containing the split key + part identified by the key part identifier. Optional, defaults + to None. Required for read/write. + + """ + super(SplitKey, self).__init__(enums.Tags.SPLIT_KEY) + + self._split_key_parts = None + self._key_part_identifier = None + self._split_key_threshold = None + self._split_key_method = None + self._prime_field_size = None + self._key_block = None + self.split_key_parts = split_key_parts self.key_part_identifier = key_part_identifier self.split_key_threshold = split_key_threshold self.split_key_method = split_key_method self.prime_field_size = prime_field_size self.key_block = key_block - self.validate() - def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0): - super(SplitKey, self).read(istream, kmip_version=kmip_version) - tstream = BytearrayStream(istream.read(self.length)) + @property + def split_key_parts(self): + if self._split_key_parts is not None: + return self._split_key_parts.value + return None + + @split_key_parts.setter + def split_key_parts(self, value): + if value is None: + self._split_key_parts = None + elif isinstance(value, six.integer_types): + self._split_key_parts = primitives.Integer( + value=value, + tag=enums.Tags.SPLIT_KEY_PARTS + ) + else: + raise TypeError("The split key parts must be an integer.") - self.split_key_parts = SplitKey.SplitKeyParts() - self.split_key_parts.read(tstream, kmip_version=kmip_version) + @property + def key_part_identifier(self): + if self._key_part_identifier is not None: + return self._key_part_identifier.value + return None + + @key_part_identifier.setter + def key_part_identifier(self, value): + if value is None: + self._key_part_identifier = None + elif isinstance(value, six.integer_types): + self._key_part_identifier = primitives.Integer( + value=value, + tag=enums.Tags.KEY_PART_IDENTIFIER + ) + else: + raise TypeError("The key part identifier must be an integer.") - self.key_part_identifier = SplitKey.KeyPartIdentifier() - self.key_part_identifier.read(tstream, kmip_version=kmip_version) + @property + def split_key_threshold(self): + if self._split_key_threshold is not None: + return self._split_key_threshold.value + return None + + @split_key_threshold.setter + def split_key_threshold(self, value): + if value is None: + self._split_key_threshold = None + elif isinstance(value, six.integer_types): + self._split_key_threshold = primitives.Integer( + value=value, + tag=enums.Tags.SPLIT_KEY_THRESHOLD + ) + else: + raise TypeError("The split key threshold must be an integer.") - self.split_key_threshold = SplitKey.SplitKeyThreshold() - self.split_key_threshold.read(tstream, kmip_version=kmip_version) + @property + def split_key_method(self): + if self._split_key_method is not None: + return self._split_key_method.value + return None + + @split_key_method.setter + def split_key_method(self, value): + if value is None: + self._split_key_method = None + elif isinstance(value, enums.SplitKeyMethod): + self._split_key_method = primitives.Enumeration( + enums.SplitKeyMethod, + value=value, + tag=enums.Tags.SPLIT_KEY_METHOD + ) + else: + raise TypeError( + "The split key method must be a SplitKeyMethod enumeration." + ) + + @property + def prime_field_size(self): + if self._prime_field_size is not None: + return self._prime_field_size.value + return None + + @prime_field_size.setter + def prime_field_size(self, value): + if value is None: + self._prime_field_size = None + elif isinstance(value, six.integer_types): + self._prime_field_size = primitives.BigInteger( + value=value, + tag=enums.Tags.PRIME_FIELD_SIZE + ) + else: + raise TypeError("The prime field size must be an integer.") - if self.is_tag_next(Tags.PRIME_FIELD_SIZE, tstream): - self.prime_field_size = SplitKey.PrimeFieldSize() - self.prime_field_size.read(tstream, kmip_version=kmip_version) + @property + def key_block(self): + if self._key_block is not None: + return self._key_block + return None + + @key_block.setter + def key_block(self, value): + if value is None: + self._key_block = None + elif isinstance(value, objects.KeyBlock): + self._key_block = value + else: + raise TypeError("The key block must be a KeyBlock structure.") - self.key_block = KeyBlock() - self.key_block.read(tstream, kmip_version=kmip_version) + def read(self, input_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Read the data encoding the SplitKey object and decode it. - self.is_oversized(tstream) - self.validate() + Args: + input_buffer (stream): A data stream containing the encoded object + data, supporting a read method; usually a BytearrayStream + object. + kmip_version (KMIPVersion): An enumeration defining the KMIP + version with which the object will be decoded. Optional, + defaults to KMIP 1.0. + """ + super(SplitKey, self).read(input_buffer, kmip_version=kmip_version) + local_buffer = utils.BytearrayStream(input_buffer.read(self.length)) - def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0): - tstream = BytearrayStream() + if self.is_tag_next(enums.Tags.SPLIT_KEY_PARTS, local_buffer): + self._split_key_parts = primitives.Integer( + tag=enums.Tags.SPLIT_KEY_PARTS + ) + self._split_key_parts.read(local_buffer, kmip_version=kmip_version) + else: + raise exceptions.InvalidKmipEncoding( + "The SplitKey encoding is missing the SplitKeyParts field." + ) + + if self.is_tag_next(enums.Tags.KEY_PART_IDENTIFIER, local_buffer): + self._key_part_identifier = primitives.Integer( + tag=enums.Tags.KEY_PART_IDENTIFIER + ) + self._key_part_identifier.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The SplitKey encoding is missing the KeyPartIdentifier field." + ) + + if self.is_tag_next(enums.Tags.SPLIT_KEY_THRESHOLD, local_buffer): + self._split_key_threshold = primitives.Integer( + tag=enums.Tags.SPLIT_KEY_THRESHOLD + ) + self._split_key_threshold.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The SplitKey encoding is missing the SplitKeyThreshold field." + ) + + if self.is_tag_next(enums.Tags.SPLIT_KEY_METHOD, local_buffer): + self._split_key_method = primitives.Enumeration( + enums.SplitKeyMethod, + tag=enums.Tags.SPLIT_KEY_METHOD + ) + self._split_key_method.read( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidKmipEncoding( + "The SplitKey encoding is missing the SplitKeyMethod field." + ) + + if self.is_tag_next(enums.Tags.PRIME_FIELD_SIZE, local_buffer): + self._prime_field_size = primitives.BigInteger( + tag=enums.Tags.PRIME_FIELD_SIZE + ) + self._prime_field_size.read( + local_buffer, + kmip_version=kmip_version + ) + else: + corner_case = enums.SplitKeyMethod.POLYNOMIAL_SHARING_PRIME_FIELD + if self.split_key_method == corner_case: + raise exceptions.InvalidKmipEncoding( + "The SplitKey encoding is missing the PrimeFieldSize " + "field. This field is required when the SplitKeyMethod is " + "PolynomialSharingPrimeField." + ) + + if self.is_tag_next(enums.Tags.KEY_BLOCK, local_buffer): + self._key_block = objects.KeyBlock() + self._key_block.read(local_buffer, kmip_version=kmip_version) + else: + raise exceptions.InvalidKmipEncoding( + "The SplitKey encoding is missing the KeyBlock field." + ) - self.split_key_parts.write(tstream, kmip_version=kmip_version) - self.key_part_identifier.write(tstream, kmip_version=kmip_version) - self.split_key_threshold.write(tstream, kmip_version=kmip_version) - self.split_key_method.write(tstream, kmip_version=kmip_version) + self.is_oversized(local_buffer) - if self.prime_field_size is not None: - self.prime_field_size.write(tstream, kmip_version=kmip_version) + def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0): + """ + Write the data encoding the SplitKey object to a buffer. - self.key_block.write(tstream, kmip_version=kmip_version) + Args: + output_buffer (stream): A data stream in which to encode object + data, supporting a write method; usually a BytearrayStream + object. + kmip_version (KMIPVersion): An enumeration defining the KMIP + version with which the object will be encoded. Optional, + defaults to KMIP 1.0. + """ + local_buffer = utils.BytearrayStream() - # Write the length and value of the template attribute - self.length = tstream.length() - super(SplitKey, self).write(ostream, kmip_version=kmip_version) - ostream.write(tstream.buffer) + if self._split_key_parts: + self._split_key_parts.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The SplitKey object is missing the SplitKeyParts field." + ) + + if self._key_part_identifier: + self._key_part_identifier.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The SplitKey object is missing the KeyPartIdentifier field." + ) + + if self._split_key_threshold: + self._split_key_threshold.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The SplitKey object is missing the SplitKeyThreshold field." + ) + + if self._split_key_method: + self._split_key_method.write( + local_buffer, + kmip_version=kmip_version + ) + else: + raise exceptions.InvalidField( + "The SplitKey object is missing the SplitKeyMethod field." + ) + + if self._prime_field_size: + self._prime_field_size.write( + local_buffer, + kmip_version=kmip_version + ) + else: + corner_case = enums.SplitKeyMethod.POLYNOMIAL_SHARING_PRIME_FIELD + if self.split_key_method == corner_case: + raise exceptions.InvalidField( + "The SplitKey object is missing the PrimeFieldSize field. " + "This field is required when the SplitKeyMethod is " + "PolynomialSharingPrimeField." + ) - def validate(self): - self.__validate() + if self._key_block: + self._key_block.write(local_buffer, kmip_version=kmip_version) + else: + raise exceptions.InvalidField( + "The SplitKey object is missing the KeyBlock field." + ) + + self.length = local_buffer.length() + super(SplitKey, self).write(output_buffer, kmip_version=kmip_version) + output_buffer.write(local_buffer.buffer) - def __validate(self): - # TODO (peter-hamilton) Finish implementation. - pass + def __repr__(self): + args = [ + "split_key_parts={}".format(repr(self.split_key_parts)), + "key_part_identifier={}".format(repr(self.key_part_identifier)), + "split_key_threshold={}".format(repr(self.split_key_threshold)), + "split_key_method={}".format(self.split_key_method), + "prime_field_size={}".format(repr(self.prime_field_size)), + "key_block={}".format(repr(self.key_block)) + ] + return "SplitKey({})".format(", ".join(args)) + + def __str__(self): + # TODO (peter-hamilton) Replace str() call below with a dict() call. + value = ", ".join( + [ + '"split_key_parts": {}'.format(self.split_key_parts), + '"key_part_identifier": {}'.format(self.key_part_identifier), + '"split_key_threshold": {}'.format(self.split_key_threshold), + '"split_key_method": {}'.format(self.split_key_method), + '"prime_field_size": {}'.format(self.prime_field_size), + '"key_block": {}'.format(str(self.key_block)) + ] + ) + return "{" + value + "}" + + def __eq__(self, other): + if isinstance(other, SplitKey): + if self.split_key_parts != other.split_key_parts: + return False + elif self.key_part_identifier != other.key_part_identifier: + return False + elif self.split_key_threshold != other.split_key_threshold: + return False + elif self.split_key_method != other.split_key_method: + return False + elif self.prime_field_size != other.prime_field_size: + return False +# elif self.key_block != other.key_block: +# return False + return True + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, SplitKey): + return not self.__eq__(other) + else: + return NotImplemented # 2.2.6 diff -Nru python-pykmip-0.9.1/kmip/demos/pie/delete_attribute.py python-pykmip-0.10.0/kmip/demos/pie/delete_attribute.py --- python-pykmip-0.9.1/kmip/demos/pie/delete_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/demos/pie/delete_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,57 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import sys + +from kmip.core import enums +from kmip.demos import utils + +from kmip.pie import client + + +# NOTE: This demo script shows how to delete the first Name attribute from +# the user-specified object. The object *must* have at least one Name +# attribute for attribute deletion to work. Otherwise, the client +# call to delete_attribute will fail. + +if __name__ == '__main__': + logger = utils.build_console_logger(logging.INFO) + + parser = utils.build_cli_parser(enums.Operation.DELETE_ATTRIBUTE) + opts, args = parser.parse_args(sys.argv[1:]) + + if opts.uuid is None: + logger.error("No UUID provided, existing early from demo.") + sys.exit() + + with client.ProxyKmipClient( + config=opts.config, + config_file=opts.config_file + ) as c: + try: + object_id, modified_attribute = c.delete_attribute( + unique_identifier=opts.uuid, + attribute_name="Name", + attribute_index=0 + ) + logger.info( + "Successfully deleted 'Name' attribute from object: {}".format( + object_id + ) + ) + logger.info("Deleted attribute: {}".format(modified_attribute)) + except Exception as e: + logger.error(e) diff -Nru python-pykmip-0.9.1/kmip/demos/pie/locate.py python-pykmip-0.10.0/kmip/demos/pie/locate.py --- python-pykmip-0.9.1/kmip/demos/pie/locate.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/demos/pie/locate.py 2020-02-25 16:05:27.000000000 +0000 @@ -13,44 +13,199 @@ # License for the specific language governing permissions and limitations # under the License. -from kmip.core.enums import NameType -from kmip.core.enums import Operation - -from kmip.core.attributes import Name - -from kmip.core.objects import Attribute +import calendar +import logging +import sys +import time +from kmip.core import enums +from kmip.core.factories.attributes import AttributeFactory from kmip.demos import utils - from kmip.pie import client -import logging -import sys - if __name__ == '__main__': logger = utils.build_console_logger(logging.INFO) # Build and parse arguments - parser = utils.build_cli_parser(Operation.LOCATE) + parser = utils.build_cli_parser(enums.Operation.LOCATE) opts, args = parser.parse_args(sys.argv[1:]) config = opts.config + offset_items = opts.offset_items + maximum_items = opts.maximum_items name = opts.name + initial_dates = opts.initial_dates + state = opts.state + object_type = opts.object_type + certificate_type = opts.certificate_type + cryptographic_algorithm = opts.cryptographic_algorithm + cryptographic_length = opts.cryptographic_length + cryptographic_usage_masks = opts.cryptographic_usage_masks + unique_identifier = opts.unique_identifier + operation_policy_name = opts.operation_policy_name + + attribute_factory = AttributeFactory() + + if offset_items and (offset_items < 0): + logger.error("Invalid offset items value provided.") + sys.exit(-1) + if maximum_items and (maximum_items < 0): + logger.error("Invalid maximum items value provided.") + sys.exit(-1) + + # Build attributes if any are specified + attributes = [] + if name: + attributes.append( + attribute_factory.create_attribute(enums.AttributeType.NAME, name) + ) + for initial_date in initial_dates: + try: + t = time.strptime(initial_date) + except ValueError, TypeError: + logger.error( + "Invalid initial date provided: {}".format(initial_date) + ) + logger.info( + "Date values should be formatted like this: " + "'Tue Jul 23 18:39:01 2019'" + ) + sys.exit(-1) - # Exit early if name is not specified - if name is None: - logger.error('No name provided, exiting early from demo') - sys.exit() - - # Build name attribute - # TODO Push this into the AttributeFactory - attribute_name = Attribute.AttributeName('Name') - name_value = Name.NameValue(name) - name_type = Name.NameType(NameType.UNINTERPRETED_TEXT_STRING) - value = Name.create(name_value=name_value, name_type=name_type) - name_obj = Attribute(attribute_name=attribute_name, attribute_value=value) - attributes = [name_obj] + try: + t = calendar.timegm(t) + except Exception: + logger.error( + "Failed to convert initial date time tuple " + "to an integer: {}".format(t) + ) + sys.exit(-2) + + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + t + ) + ) + if state: + state = getattr(enums.State, state, None) + if state: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.STATE, + state + ) + ) + else: + logger.error("Invalid state provided: {}".format(opts.state)) + sys.exit(-3) + if object_type: + object_type = getattr(enums.ObjectType, object_type, None) + if object_type: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_TYPE, + object_type + ) + ) + else: + logger.error( + "Invalid object type provided: {}".format(opts.object_type) + ) + sys.exit(-4) + if cryptographic_algorithm: + cryptographic_algorithm = getattr( + enums.CryptographicAlgorithm, + cryptographic_algorithm, + None + ) + if cryptographic_algorithm: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + cryptographic_algorithm + ) + ) + else: + logger.error( + "Invalid cryptographic algorithm provided: {}".format( + opts.cryptographic_algorithm + ) + ) + sys.exit(-5) + if cryptographic_length: + if cryptographic_length > 0: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + cryptographic_length + ) + ) + else: + logger.error( + "Invalid cryptographic length provided: {}".format( + opts.cryptographic_length + ) + ) + sys.exit(-6) + if cryptographic_usage_masks: + masks = [] + for cryptographic_usage_mask in cryptographic_usage_masks: + mask = getattr( + enums.CryptographicUsageMask, + cryptographic_usage_mask, + None + ) + if mask: + masks.append(mask) + else: + logger.error( + "Invalid cryptographic usage mask provided: {}".format( + cryptographic_usage_mask + ) + ) + sys.exit(-7) + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + masks + ) + ) + if certificate_type: + certificate_type = getattr( + enums.CertificateType, + certificate_type, + None + ) + if certificate_type: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.CERTIFICATE_TYPE, + certificate_type + ) + ) + else: + logger.error( + "Invalid certificate type provided: {}".format( + opts.certificate_type + ) + ) + sys.exit(-8) + if unique_identifier: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + unique_identifier + ) + ) + if operation_policy_name: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + operation_policy_name + ) + ) # Build the client and connect to the server with client.ProxyKmipClient( @@ -58,7 +213,11 @@ config_file=opts.config_file ) as client: try: - uuids = client.locate(attributes=attributes) + uuids = client.locate( + attributes=attributes, + offset_items=offset_items, + maximum_items=maximum_items + ) logger.info("Located uuids: {0}".format(uuids)) except Exception as e: logger.error(e) diff -Nru python-pykmip-0.9.1/kmip/demos/pie/modify_attribute.py python-pykmip-0.10.0/kmip/demos/pie/modify_attribute.py --- python-pykmip-0.9.1/kmip/demos/pie/modify_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/demos/pie/modify_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,63 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import sys + +from kmip.core.factories import attributes +from kmip.core import enums +from kmip.demos import utils + +from kmip.pie import client + + +# NOTE: This demo script shows how to modify the first Name attribute on +# the user-specified object. The object *must* have at least one Name +# attribute for attribute modification to work. Otherwise, the client +# call to modify_attribute will fail. + +if __name__ == '__main__': + logger = utils.build_console_logger(logging.INFO) + + parser = utils.build_cli_parser(enums.Operation.MODIFY_ATTRIBUTE) + opts, args = parser.parse_args(sys.argv[1:]) + + if opts.uuid is None: + logger.error("No UUID provided, existing early from demo.") + sys.exit() + + factory = attributes.AttributeFactory() + + with client.ProxyKmipClient( + config=opts.config, + config_file=opts.config_file + ) as c: + try: + object_id, modified_attribute = c.modify_attribute( + unique_identifier=opts.uuid, + attribute=factory.create_attribute( + enums.AttributeType.NAME, + "Modified Name", + index=0 + ) + ) + logger.info( + "Successfully modified 'Name' attribute on object: {}".format( + object_id + ) + ) + logger.info("Modified attribute: {}".format(modified_attribute)) + except Exception as e: + logger.error(e) diff -Nru python-pykmip-0.9.1/kmip/demos/pie/register_split_key.py python-pykmip-0.10.0/kmip/demos/pie/register_split_key.py --- python-pykmip-0.9.1/kmip/demos/pie/register_split_key.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/demos/pie/register_split_key.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,64 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import sys + +from kmip.core import enums +from kmip.demos import utils + +from kmip.pie import client +from kmip.pie import objects + + +if __name__ == '__main__': + logger = utils.build_console_logger(logging.INFO) + + parser = utils.build_cli_parser(enums.Operation.REGISTER) + opts, args = parser.parse_args(sys.argv[1:]) + + config = opts.config + + split_key = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ), + name="Demo Split Key", + cryptographic_usage_masks=[enums.CryptographicUsageMask.EXPORT], + key_format_type=enums.KeyFormatType.RAW, + key_wrapping_data=None, + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.XOR, + prime_field_size=None + ) + split_key.operation_policy_name = opts.operation_policy_name + + # Build the client and connect to the server + with client.ProxyKmipClient( + config=config, + config_file=opts.config_file + ) as client: + try: + uid = client.register(split_key) + logger.info( + "Successfully registered split key with ID: {0}".format(uid) + ) + except Exception as e: + logger.error(e) diff -Nru python-pykmip-0.9.1/kmip/demos/pie/set_attribute.py python-pykmip-0.10.0/kmip/demos/pie/set_attribute.py --- python-pykmip-0.9.1/kmip/demos/pie/set_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/demos/pie/set_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,62 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import sys + +from kmip.core.factories import attributes +from kmip.core import enums +from kmip.demos import utils + +from kmip.pie import client + + +# NOTE: This demo script shows how to set the Sensitive attribute on +# the user-specified object. The server must support KMIP 2.0, since +# the SetAttribute operation is KMIP 2.0+ only and the Sensitive +# attribute is KMIP 1.4+ only. Otherwise, the client call to +# set_attribute will fail. + +if __name__ == '__main__': + logger = utils.build_console_logger(logging.INFO) + + parser = utils.build_cli_parser(enums.Operation.SET_ATTRIBUTE) + opts, args = parser.parse_args(sys.argv[1:]) + + if opts.uuid is None: + logger.error("No UUID provided, existing early from demo.") + sys.exit() + + factory = attributes.AttributeFactory() + + with client.ProxyKmipClient( + config=opts.config, + config_file=opts.config_file, + kmip_version=enums.KMIPVersion.KMIP_2_0 + ) as c: + try: + object_id = c.set_attribute( + unique_identifier=opts.uuid, + attribute_name="Sensitive", + attribute_value=True + ) + logger.info( + "Successfully set the 'Sensitive' attribute on object: " + "{}".format( + object_id + ) + ) + except Exception as e: + logger.error(e) diff -Nru python-pykmip-0.9.1/kmip/demos/units/locate.py python-pykmip-0.10.0/kmip/demos/units/locate.py --- python-pykmip-0.9.1/kmip/demos/units/locate.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/demos/units/locate.py 2020-02-25 16:05:27.000000000 +0000 @@ -13,42 +13,40 @@ # License for the specific language governing permissions and limitations # under the License. -from kmip.core.enums import CredentialType -from kmip.core.enums import NameType -from kmip.core.enums import Operation -from kmip.core.enums import ResultStatus - -from kmip.core.attributes import Name +import calendar +import logging +import sys +import time +from kmip.core import enums from kmip.core.factories.attributes import AttributeFactory from kmip.core.factories.credentials import CredentialFactory - -from kmip.core.objects import Attribute - from kmip.demos import utils - -from kmip.services.kmip_client import KMIPProxy - -import logging -import sys +from kmip.services import kmip_client if __name__ == '__main__': logger = utils.build_console_logger(logging.INFO) # Build and parse arguments - parser = utils.build_cli_parser(Operation.LOCATE) + parser = utils.build_cli_parser(enums.Operation.LOCATE) opts, args = parser.parse_args(sys.argv[1:]) username = opts.username password = opts.password config = opts.config + offset_items = opts.offset_items + maximum_items = opts.maximum_items name = opts.name - - # Exit early if the UUID is not specified - if name is None: - logger.error('No name provided, exiting early from demo') - sys.exit() + initial_dates = opts.initial_dates + state = opts.state + object_type = opts.object_type + certificate_type = opts.certificate_type + cryptographic_algorithm = opts.cryptographic_algorithm + cryptographic_length = opts.cryptographic_length + cryptographic_usage_masks = opts.cryptographic_usage_masks + unique_identifier = opts.unique_identifier + operation_policy_name = opts.operation_policy_name attribute_factory = AttributeFactory() credential_factory = CredentialFactory() @@ -58,34 +56,198 @@ if (username is None) and (password is None): credential = None else: - credential_type = CredentialType.USERNAME_AND_PASSWORD - credential_value = {'Username': username, - 'Password': password} - credential = credential_factory.create_credential(credential_type, - credential_value) + credential_type = enums.CredentialType.USERNAME_AND_PASSWORD + credential_value = { + "Username": username, + "Password": password + } + credential = credential_factory.create_credential( + credential_type, + credential_value + ) + + if offset_items and (offset_items < 0): + logger.error("Invalid offset items value provided.") + sys.exit(-1) + if maximum_items and (maximum_items < 0): + logger.error("Invalid maximum items value provided.") + sys.exit(-1) + # Build the client and connect to the server - client = KMIPProxy(config=config, config_file=opts.config_file) + client = kmip_client.KMIPProxy(config=config, config_file=opts.config_file) client.open() - # Build name attribute - # TODO (peter-hamilton) Push this into the AttributeFactory - attribute_name = Attribute.AttributeName('Name') - name_value = Name.NameValue(name) - name_type = Name.NameType(NameType.UNINTERPRETED_TEXT_STRING) - value = Name.create(name_value=name_value, name_type=name_type) - name_obj = Attribute(attribute_name=attribute_name, attribute_value=value) - attributes = [name_obj] - - # Locate UUID of specified SYMMETRIC_KEY object - result = client.locate(attributes=attributes, - credential=credential) + # Build attributes if any are specified + attributes = [] + if name: + attributes.append( + attribute_factory.create_attribute(enums.AttributeType.NAME, name) + ) + for initial_date in initial_dates: + try: + t = time.strptime(initial_date) + except ValueError: + logger.error( + "Invalid initial date provided: {}".format(initial_date) + ) + logger.info( + "Date values should be formatted like this: " + "'Tue Jul 23 18:39:01 2019'" + ) + sys.exit(-1) + + try: + t = calendar.timegm(t) + except Exception: + logger.error( + "Failed to convert initial date time tuple " + "to an integer: {}".format(t) + ) + sys.exit(-2) + + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + t + ) + ) + if state: + state = getattr(enums.State, state, None) + if state: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.STATE, + state + ) + ) + else: + logger.error("Invalid state provided: {}".format(opts.state)) + client.close() + sys.exit(-3) + if object_type: + object_type = getattr(enums.ObjectType, object_type, None) + if object_type: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_TYPE, + object_type + ) + ) + else: + logger.error( + "Invalid object type provided: {}".format(opts.object_type) + ) + client.close() + sys.exit(-4) + if cryptographic_algorithm: + cryptographic_algorithm = getattr( + enums.CryptographicAlgorithm, + cryptographic_algorithm, + None + ) + if cryptographic_algorithm: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + cryptographic_algorithm + ) + ) + else: + logger.error( + "Invalid cryptographic algorithm provided: {}".format( + opts.cryptographic_algorithm + ) + ) + client.close() + sys.exit(-5) + if cryptographic_length: + if cryptographic_length > 0: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + cryptographic_length + ) + ) + else: + logger.error( + "Invalid cryptographic length provided: {}".format( + opts.cryptographic_length + ) + ) + client.close() + sys.exit(-6) + if cryptographic_usage_masks: + masks = [] + for cryptographic_usage_mask in cryptographic_usage_masks: + mask = getattr( + enums.CryptographicUsageMask, + cryptographic_usage_mask, + None + ) + if mask: + masks.append(mask) + else: + logger.error( + "Invalid cryptographic usage mask provided: {}".format( + cryptographic_usage_mask + ) + ) + sys.exit(-7) + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + masks + ) + ) + if certificate_type: + certificate_type = getattr( + enums.CertificateType, + certificate_type, + None + ) + if certificate_type: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.CERTIFICATE_TYPE, + certificate_type + ) + ) + else: + logger.error( + "Invalid certificate type provided: {}".format( + opts.certificate_type + ) + ) + client.close() + sys.exit(-8) + if unique_identifier: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + unique_identifier + ) + ) + if operation_policy_name: + attributes.append( + attribute_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + operation_policy_name + ) + ) + + result = client.locate( + attributes=attributes, + offset_items=offset_items, + maximum_items=maximum_items, + credential=credential + ) client.close() # Display operation results logger.info('locate() result status: {0}'.format( result.result_status.value)) - if result.result_status.value == ResultStatus.SUCCESS: + if result.result_status.value == enums.ResultStatus.SUCCESS: logger.info('located UUIDs:') for uuid in result.uuids: logger.info('{0}'.format(uuid)) diff -Nru python-pykmip-0.9.1/kmip/demos/utils.py python-pykmip-0.10.0/kmip/demos/utils.py --- python-pykmip-0.9.1/kmip/demos/utils.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/demos/utils.py 2020-02-25 16:05:27.000000000 +0000 @@ -56,7 +56,7 @@ return logger -def build_cli_parser(operation=None): +def build_cli_parser(operation): # Build the argument parser and setup expected options parser = optparse.OptionParser( usage="%prog [options]", @@ -229,8 +229,51 @@ dest="attribute_names", help="List of attribute names to retrieve, defaults to all " "attributes") + elif operation is Operation.MODIFY_ATTRIBUTE: + parser.add_option( + "-i", + "--uuid", + action="store", + type="str", + default=None, + dest="uuid", + help="UID of a managed object") + elif operation is Operation.DELETE_ATTRIBUTE: + parser.add_option( + "-i", + "--uuid", + action="store", + type="str", + default=None, + dest="uuid", + help="UID of a managed object") + elif operation is Operation.SET_ATTRIBUTE: + parser.add_option( + "-i", + "--uuid", + action="store", + type="str", + default=None, + dest="uuid", + help="UID of a managed object") elif operation is Operation.LOCATE: parser.add_option( + "--offset-items", + action="store", + type="int", + default=None, + dest="offset_items", + help="The number of matching secrets to skip." + ) + parser.add_option( + "--maximum-items", + action="store", + type="int", + default=None, + dest="maximum_items", + help="The maximum number of matching secrets to return." + ) + parser.add_option( "-n", "--name", action="store", @@ -238,6 +281,94 @@ default=None, dest="name", help="Name of secret to retrieve from the KMIP server") + parser.add_option( + "--initial-date", + action="append", + type="str", + default=[], + dest="initial_dates", + help=( + "Initial date(s) in UTC of the secret to retrieve from the " + "KMIP server. Use once to perform an exact date match. Use " + "twice to create a date range that the secret's date should " + "be within. The value format should look like this: " + "'Tue Jul 23 18:39:01 2019'" + ) + ) + parser.add_option( + "--state", + action="store", + type="str", + default=None, + dest="state", + help="The state of the secret (e.g., PRE_ACTIVE, ACTIVE)" + ) + parser.add_option( + "--object-type", + action="store", + type="str", + default=None, + dest="object_type", + help=( + "The object type of the secret " + "(e.g., CERTIFICATE, SYMMETRIC_KEY)" + ) + ) + parser.add_option( + "--certificate-type", + action="store", + type="str", + default=None, + dest="certificate_type", + help="The certificate type of the secret (e.g., X_509)" + ) + parser.add_option( + "--cryptographic-algorithm", + action="store", + type="str", + default=None, + dest="cryptographic_algorithm", + help="The cryptographic algorithm of the secret (e.g., AES, RSA)" + ) + parser.add_option( + "--cryptographic-length", + action="store", + type="int", + default=None, + dest="cryptographic_length", + help="The cryptographic length of the secret (e.g., 128, 2048)" + ) + parser.add_option( + "--cryptographic-usage-mask", + action="append", + type="str", + default=[], + dest="cryptographic_usage_masks", + help=( + "The cryptographic usage mask(s) the secret should have set " + "(e.g., ENCRYPT, DECRYPT). Use multiple times to specify " + "multiple cryptographic usage mask enumeration values. All " + "values will get combined into a single mask when sent to the " + "server." + ) + ) + parser.add_option( + "-i", + "--unique-identifier", + action="store", + type="str", + default=None, + dest="unique_identifier", + help="The unique identifier of the secret (e.g., 1, 2, 3)" + ) + parser.add_option( + "--operation-policy-name", + action="store", + type="str", + default=None, + dest="operation_policy_name", + help="The operation policy name of the secret (e.g., default)" + ) elif operation is Operation.REGISTER: parser.add_option( "-f", @@ -595,6 +726,8 @@ log_public_key(logger, secret_value) elif secret_type is ObjectType.SYMMETRIC_KEY: log_symmetric_key(logger, secret_value) + elif secret_type is ObjectType.SPLIT_KEY: + log_split_key(logger, secret_value) else: logger.info('generic secret: {0}'.format(secret_value)) @@ -625,9 +758,23 @@ log_key_block(logger, key_block) +def log_split_key(logger, split_key): + logger.info("Split Key:") + logger.info("* Split Key Parts: {}".format(split_key.split_key_parts)) + logger.info( + "* Key Part Identifier: {}".format(split_key.key_part_identifier) + ) + logger.info( + "* Split Key Threshold: {}".format(split_key.split_key_threshold) + ) + logger.info("* Split Key Method: {}".format(split_key.split_key_method)) + logger.info("* Prime Field Size: {}".format(split_key.prime_field_size)) + log_key_block(logger, split_key.key_block) + + def log_key_block(logger, key_block): if key_block is not None: - logger.info('key block:') + logger.info("* Key Block:") key_format_type = key_block.key_format_type key_compression_type = key_block.key_compression_type @@ -636,33 +783,25 @@ cryptographic_length = key_block.cryptographic_length key_wrapping_data = key_block.key_wrapping_data - logger.info('* key format type: {0}'.format(key_format_type)) - logger.info('* key compression type: {0}'.format( + logger.info(" * Key Format Type: {}".format(key_format_type)) + logger.info(" * Key Compression Type: {}".format( key_compression_type)) - logger.info('* cryptographic algorithm: {0}'.format( + logger.info(" * Cryptographic Algorithm: {}".format( cryptographic_algorithm)) - logger.info('* cryptographic length: {0}'.format( + logger.info(" * Cryptographic Length: {}".format( cryptographic_length)) + logger.info(" * Key Wrapping Data: {}".format(key_wrapping_data)) log_key_value(logger, key_value) - log_key_wrapping_data(logger, key_wrapping_data) else: - logger.info('key block: {0}'.format(key_block)) + logger.info("* Key Block: {}".format(key_block)) def log_key_value(logger, key_value): if key_value is not None: - logger.info('key value:') - - key_material = key_value.key_material - attributes = key_value.attributes - - logger.info('key material: {0}'.format(repr(key_material))) - - log_attribute_list(logger, attributes) + logger.info(" * Key Value:") + logger.info( + " * Key Material: {}".format(repr(key_value.key_material)) + ) else: - logger.info('key value: {0}'.format(key_value)) - - -def log_key_wrapping_data(logger, key_wrapping_data): - logger.info('key wrapping data: {0}'.format(key_wrapping_data)) + logger.info(" * Key Value: {}".format(key_value)) diff -Nru python-pykmip-0.9.1/kmip/__init__.py python-pykmip-0.10.0/kmip/__init__.py --- python-pykmip-0.9.1/kmip/__init__.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/__init__.py 2020-02-25 16:05:27.000000000 +0000 @@ -15,6 +15,8 @@ import os import re +import sys +import warnings from kmip.core import enums from kmip.pie import client @@ -44,3 +46,22 @@ 'objects', 'services' ] + + +if sys.version_info[:2] == (2, 7): + warnings.warn( + ( + "PyKMIP will drop support for Python 2.7 in a future release. " + "Please upgrade to a newer version of Python (3.5+ preferred)." + ), + PendingDeprecationWarning + ) + +if sys.version_info[:2] == (3, 4): + warnings.warn( + ( + "PyKMIP will drop support for Python 3.4 in a future release. " + "Please upgrade to a newer version of Python (3.5+ preferred)." + ), + PendingDeprecationWarning + ) diff -Nru python-pykmip-0.9.1/kmip/pie/client.py python-pykmip-0.10.0/kmip/pie/client.py --- python-pykmip-0.9.1/kmip/pie/client.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/pie/client.py 2020-02-25 16:05:27.000000000 +0000 @@ -25,6 +25,8 @@ from kmip.core.attributes import CryptographicParameters from kmip.core.attributes import DerivationParameters +from kmip.core.messages import payloads + from kmip.pie import exceptions from kmip.pie import factory from kmip.pie import objects as pobjects @@ -100,6 +102,7 @@ self.logger = logging.getLogger(__name__) self.attribute_factory = attributes.AttributeFactory() + self.attribute_value_factory = self.attribute_factory.value_factory self.object_factory = factory.ObjectFactory() # TODO (peter-hamilton) Consider adding validation checks for inputs. @@ -238,7 +241,14 @@ key_attributes.extend(common_attributes) if name: - key_attributes.extend(self._build_name_attribute(name)) + key_attributes.extend( + [ + self.attribute_factory.create_attribute( + enums.AttributeType.NAME, + name + ) + ] + ) template = cobjects.TemplateAttribute(attributes=key_attributes) @@ -320,39 +330,45 @@ # Create public / private specific attributes public_template = None - names = None - if public_name: - names = self._build_name_attribute(name=public_name) attrs = [] + if public_name: + attrs.append( + self.attribute_factory.create_attribute( + enums.AttributeType.NAME, + public_name + ) + ) if public_usage_mask: - attrs = [ + attrs.append( self.attribute_factory.create_attribute( enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, public_usage_mask ) - ] - if names or attrs: + ) + if attrs: public_template = cobjects.TemplateAttribute( - names=names, attributes=attrs, tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE ) private_template = None - names = None - if private_name: - names = self._build_name_attribute(name=private_name) attrs = [] + if private_name: + attrs.append( + self.attribute_factory.create_attribute( + enums.AttributeType.NAME, + private_name + ) + ) if private_usage_mask: - attrs = [ + attrs.append( self.attribute_factory.create_attribute( enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, private_usage_mask ) - ] - if names or attrs: + ) + if attrs: private_template = cobjects.TemplateAttribute( - names=names, attributes=attrs, tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE ) @@ -374,6 +390,127 @@ raise exceptions.KmipOperationFailure(status, reason, message) @is_connected + def delete_attribute(self, unique_identifier=None, **kwargs): + """ + Delete an attribute from a KMIP managed object. + + Args: + unique_identifier (string): The ID of the managed object. + **kwargs (various): A placeholder for attribute values used to + identify the attribute to delete. For KMIP 1.0 - 1.4, the + supported parameters are: + attribute_name (string): The name of the attribute to + delete. Required. + attribute_index (int): The index of the attribute to + delete. Defaults to zero. + For KMIP 2.0+, the supported parameters are: + current_attribute (struct): A CurrentAttribute object + containing the attribute to delete. Required if the + attribute reference is not specified. + attribute_reference (struct): An AttributeReference + object containing the name of the attribute to + delete. Required if the current attribute is not + specified. + + Returns: + string: The ID of the managed object the attribute was deleted + from. + struct: A Primitive object representing the deleted attribute. + Only returned if used for KMIP 1.0 - 1.4 messages. + """ + request_payload = payloads.DeleteAttributeRequestPayload( + unique_identifier=unique_identifier, + attribute_name=kwargs.get("attribute_name"), + attribute_index=kwargs.get("attribute_index"), + current_attribute=kwargs.get("current_attribute"), + attribute_reference=kwargs.get("attribute_reference") + ) + response_payload = self.proxy.send_request_payload( + enums.Operation.DELETE_ATTRIBUTE, + request_payload + ) + + return response_payload.unique_identifier, response_payload.attribute + + @is_connected + def set_attribute(self, unique_identifier=None, **kwargs): + """ + Set an attribute on a KMIP managed object. + + Args: + unique_identifier (string): The ID of the managed object. + **kwargs (various): A placeholder for attribute-related fields. + Supported parameters include: + attribute_name (string): The name of the attribute being + set. Required. + attribute_value (various): The value of the attribute + being set. Required. + + Here is an example. To set an object's 'sensitive' attribute + to True, specify: + attribute_name='Sensitive' + attribute_value=True + + For a list of all supported attributes, see the + AttributeValueFactory. + + Returns: + string: The ID of the managed object the attribute was set on. + """ + a = self.attribute_value_factory.create_attribute_value_by_enum( + enums.convert_attribute_name_to_tag(kwargs.get("attribute_name")), + kwargs.get("attribute_value") + ) + request_payload = payloads.SetAttributeRequestPayload( + unique_identifier=unique_identifier, + new_attribute=cobjects.NewAttribute(attribute=a) + ) + response_payload = self.proxy.send_request_payload( + enums.Operation.SET_ATTRIBUTE, + request_payload + ) + + return response_payload.unique_identifier + + @is_connected + def modify_attribute(self, unique_identifier=None, **kwargs): + """ + Set an attribute on a KMIP managed object. + + Args: + unique_identifier (string): The ID of the managed object. + **kwargs (various): A placeholder for attribute values used to + identify the attribute to modify. For KMIP 1.0 - 1.4, the + supported parameters are: + attribute (struct): An Attribute object containing the + name and index of the existing attribute and the + new value for that attribute. + For KMIP 2.0+, the supported parameters are: + current_attribute (struct): A CurrentAttribute object + containing the attribute to modify. Required if the + attribute is multivalued. + attribute_reference (struct): A NewAttribute object + containing the new attribute value. Required. + + Returns: + string: The ID of the managed object the attribute was modified on. + struct: An Attribute object representing the newly modified + attribute. Only returned if used for KMIP 1.0 - 1.4 messages. + """ + request_payload = payloads.ModifyAttributeRequestPayload( + unique_identifier=unique_identifier, + attribute=kwargs.get("attribute"), + current_attribute=kwargs.get("current_attribute"), + new_attribute=kwargs.get("new_attribute") + ) + response_payload = self.proxy.send_request_payload( + enums.Operation.MODIFY_ATTRIBUTE, + request_payload + ) + + return response_payload.unique_identifier, response_payload.attribute + + @is_connected def register(self, managed_object): """ Register a managed object with a KMIP appliance. @@ -661,7 +798,7 @@ @is_connected def locate(self, maximum_items=None, storage_status_mask=None, - object_group_member=None, attributes=None): + object_group_member=None, attributes=None, offset_items=None): """ Search for managed objects, depending on the attributes specified in the request. @@ -669,6 +806,8 @@ Args: maximum_items (integer): Maximum number of object identifiers the server MAY return. + offset_items (integer): Number of object identifiers the server + should skip before returning results. storage_status_mask (integer): A bit mask that indicates whether on-line or archived objects are to be searched. object_group_member (ObjectGroupMember): An enumeration that @@ -688,6 +827,9 @@ if maximum_items is not None: if not isinstance(maximum_items, six.integer_types): raise TypeError("maximum_items must be an integer") + if offset_items is not None: + if not isinstance(offset_items, six.integer_types): + raise TypeError("offset items must be an integer") if storage_status_mask is not None: if not isinstance(storage_status_mask, six.integer_types): raise TypeError("storage_status_mask must be an integer") @@ -705,8 +847,12 @@ # Search for managed objects and handle the results result = self.proxy.locate( - maximum_items, storage_status_mask, - object_group_member, attributes) + maximum_items=maximum_items, + offset_items=offset_items, + storage_status_mask=storage_status_mask, + object_group_member=object_group_member, + attributes=attributes + ) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: @@ -1595,19 +1741,6 @@ return common_attributes - def _build_name_attribute(self, name=None): - ''' - Build a name attribute, returned in a list for ease - of use in the caller - ''' - name_list = [] - if name: - name_list.append(self.attribute_factory.create_attribute( - enums.AttributeType.NAME, - name) - ) - return name_list - def __enter__(self): self.open() return self diff -Nru python-pykmip-0.9.1/kmip/pie/factory.py python-pykmip-0.10.0/kmip/pie/factory.py --- python-pykmip-0.9.1/kmip/pie/factory.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/pie/factory.py 2020-02-25 16:05:27.000000000 +0000 @@ -68,6 +68,10 @@ return self._build_core_opaque_object(obj) elif isinstance(obj, secrets.OpaqueObject): return self._build_pie_opaque_object(obj) + elif isinstance(obj, pobjects.SplitKey): + return self._build_core_split_key(obj) + elif isinstance(obj, secrets.SplitKey): + return self._build_pie_split_key(obj) else: raise TypeError("object type unsupported and cannot be converted") @@ -125,6 +129,23 @@ value = obj.opaque_data_value.value return pobjects.OpaqueObject(value, opaque_type) + def _build_pie_split_key(self, secret): + algorithm = secret.key_block.cryptographic_algorithm.value + return pobjects.SplitKey( + cryptographic_algorithm=algorithm, + cryptographic_length=secret.key_block.cryptographic_length.value, + key_value=secret.key_block.key_value.key_material.value, + key_format_type=secret.key_block.key_format_type.value, + key_wrapping_data=self._build_key_wrapping_data( + secret.key_block.key_wrapping_data + ), + split_key_parts=secret.split_key_parts, + key_part_identifier=secret.key_part_identifier, + split_key_threshold=secret.split_key_threshold, + split_key_method=secret.split_key_method, + prime_field_size=secret.prime_field_size + ) + def _build_core_key(self, key, cls): algorithm = key.cryptographic_algorithm length = key.cryptographic_length @@ -170,6 +191,35 @@ return secrets.SecretData(data_type, key_block) + def _build_core_split_key(self, secret): + key_material = cobjects.KeyMaterial(secret.value) + key_value = cobjects.KeyValue(key_material) + key_wrapping_data = None + if secret.key_wrapping_data: + key_wrapping_data = cobjects.KeyWrappingData( + **secret.key_wrapping_data + ) + key_block = cobjects.KeyBlock( + key_format_type=misc.KeyFormatType(secret.key_format_type), + key_compression_type=None, + key_value=key_value, + cryptographic_algorithm=attributes.CryptographicAlgorithm( + secret.cryptographic_algorithm + ), + cryptographic_length=attributes.CryptographicLength( + secret.cryptographic_length + ), + key_wrapping_data=key_wrapping_data + ) + return secrets.SplitKey( + split_key_parts=secret.split_key_parts, + key_part_identifier=secret.key_part_identifier, + split_key_threshold=secret.split_key_threshold, + split_key_method=secret.split_key_method, + prime_field_size=secret.prime_field_size, + key_block=key_block + ) + def _build_core_opaque_object(self, obj): opaque_type = obj.opaque_type value = obj.value diff -Nru python-pykmip-0.9.1/kmip/pie/objects.py python-pykmip-0.10.0/kmip/pie/objects.py --- python-pykmip-0.9.1/kmip/pie/objects.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/pie/objects.py 2020-02-25 16:05:27.000000000 +0000 @@ -14,10 +14,10 @@ # under the License. from abc import abstractmethod +import sqlalchemy from sqlalchemy import Column, event, ForeignKey, Integer, String, VARBINARY from sqlalchemy import Boolean from sqlalchemy.ext.associationproxy import association_proxy -from sqlalchemy.orm import relationship import binascii import six @@ -26,6 +26,50 @@ from kmip.pie import sqltypes as sql +app_specific_info_map = sqlalchemy.Table( + "app_specific_info_map", + sql.Base.metadata, + sqlalchemy.Column( + "managed_object_id", + sqlalchemy.Integer, + sqlalchemy.ForeignKey( + "managed_objects.uid", + ondelete="CASCADE" + ) + ), + sqlalchemy.Column( + "app_specific_info_id", + sqlalchemy.Integer, + sqlalchemy.ForeignKey( + "app_specific_info.id", + ondelete="CASCADE" + ) + ) +) + + +object_group_map = sqlalchemy.Table( + "object_group_map", + sql.Base.metadata, + sqlalchemy.Column( + "managed_object_id", + sqlalchemy.Integer, + sqlalchemy.ForeignKey( + "managed_objects.uid", + ondelete="CASCADE" + ) + ), + sqlalchemy.Column( + "object_group_id", + sqlalchemy.Integer, + sqlalchemy.ForeignKey( + "object_groups.id", + ondelete="CASCADE" + ) + ) +) + + class ManagedObject(sql.Base): """ The abstract base class of the simplified KMIP object hierarchy. @@ -50,17 +94,37 @@ _class_type = Column('class_type', String(50)) value = Column('value', VARBINARY(1024)) name_index = Column(Integer, default=0) - _names = relationship('ManagedObjectName', back_populates='mo', - cascade='all, delete-orphan') + _names = sqlalchemy.orm.relationship( + "ManagedObjectName", + back_populates="mo", + cascade="all, delete-orphan", + order_by="ManagedObjectName.id" + ) names = association_proxy('_names', 'name') operation_policy_name = Column( 'operation_policy_name', String(50), default='default' ) + sensitive = Column("sensitive", Boolean, default=False) initial_date = Column(Integer, default=0) _owner = Column('owner', String(50), default=None) + app_specific_info = sqlalchemy.orm.relationship( + "ApplicationSpecificInformation", + secondary=app_specific_info_map, + back_populates="managed_objects", + order_by="ApplicationSpecificInformation.id", + passive_deletes=True + ) + object_groups = sqlalchemy.orm.relationship( + "ObjectGroup", + secondary=object_group_map, + back_populates="managed_objects", + order_by="ObjectGroup.id", + passive_deletes=True + ) + __mapper_args__ = { 'polymorphic_identity': 'ManagedObject', 'polymorphic_on': _class_type @@ -81,6 +145,7 @@ self.names = list() self.operation_policy_name = None self.initial_date = 0 + self.sensitive = False self._object_type = None self._owner = None @@ -1051,6 +1116,264 @@ sql.attribute_append_factory("name_index"), retval=False) +class SplitKey(Key): + """ + """ + + __mapper_args__ = {"polymorphic_identity": "SplitKey"} + __table_args__ = {"sqlite_autoincrement": True} + __tablename__ = "split_keys" + + unique_identifier = sqlalchemy.Column( + "uid", + sqlalchemy.Integer, + sqlalchemy.ForeignKey("keys.uid"), + primary_key=True + ) + + # Split Key object fields + _split_key_parts = sqlalchemy.Column( + "_split_key_parts", + sqlalchemy.Integer, + default=None + ) + _key_part_identifier = sqlalchemy.Column( + "_key_part_identifier", + sqlalchemy.Integer, + default=None + ) + _split_key_threshold = sqlalchemy.Column( + "_split_key_threshold", + sqlalchemy.Integer, + default=None + ) + _split_key_method = sqlalchemy.Column( + "_split_key_method", + sql.EnumType(enums.SplitKeyMethod), + default=None + ) + _prime_field_size = sqlalchemy.Column( + "_prime_field_size", + sqlalchemy.BigInteger, + default=None + ) + + def __init__(self, + cryptographic_algorithm=None, + cryptographic_length=None, + key_value=None, + cryptographic_usage_masks=None, + name="Split Key", + key_format_type=enums.KeyFormatType.RAW, + key_wrapping_data=None, + split_key_parts=None, + key_part_identifier=None, + split_key_threshold=None, + split_key_method=None, + prime_field_size=None): + """ + Create a SplitKey. + + Args: + cryptographic_algorithm(enum): A CryptographicAlgorithm enumeration + identifying the type of algorithm for the split key. Required. + cryptographic_length(int): The length in bits of the split key. + Required. + key_value(bytes): The bytes representing the split key. Required. + cryptographic_usage_masks(list): A list of CryptographicUsageMask + enumerations defining how the split key will be used. Optional, + defaults to None. + name(string): The string name of the split key. Optional, defaults + to "Split Key". + key_format_type (enum): A KeyFormatType enumeration specifying the + format of the split key. Optional, defaults to Raw. + key_wrapping_data(dict): A dictionary containing key wrapping data + settings, describing how the split key has been wrapped. + Optional, defaults to None. + split_key_parts (int): An integer specifying the total number of + parts of the split key. Required. + key_part_identifier (int): An integer specifying which key part + of the split key this key object represents. Required. + split_key_threshold (int): An integer specifying the minimum + number of key parts required to reconstruct the split key. + Required. + split_key_method (enum): A SplitKeyMethod enumeration specifying + how the key was split. Required. + prime_field_size (int): A big integer specifying the prime field + size used for the Polynomial Sharing Prime Field split key + method. Optional, defaults to None. + """ + super(SplitKey, self).__init__(key_wrapping_data=key_wrapping_data) + + self._object_type = enums.ObjectType.SPLIT_KEY + + self.key_format_type = key_format_type + self.value = key_value + self.cryptographic_algorithm = cryptographic_algorithm + self.cryptographic_length = cryptographic_length + self.names = [name] + + if cryptographic_usage_masks: + self.cryptographic_usage_masks.extend(cryptographic_usage_masks) + + self.split_key_parts = split_key_parts + self.key_part_identifier = key_part_identifier + self.split_key_threshold = split_key_threshold + self.split_key_method = split_key_method + self.prime_field_size = prime_field_size + + @property + def split_key_parts(self): + return self._split_key_parts + + @split_key_parts.setter + def split_key_parts(self, value): + if (value is None) or (isinstance(value, six.integer_types)): + self._split_key_parts = value + else: + raise TypeError("The split key parts must be an integer.") + + @property + def key_part_identifier(self): + return self._key_part_identifier + + @key_part_identifier.setter + def key_part_identifier(self, value): + if (value is None) or (isinstance(value, six.integer_types)): + self._key_part_identifier = value + else: + raise TypeError("The key part identifier must be an integer.") + + @property + def split_key_threshold(self): + return self._split_key_threshold + + @split_key_threshold.setter + def split_key_threshold(self, value): + if (value is None) or (isinstance(value, six.integer_types)): + self._split_key_threshold = value + else: + raise TypeError("The split key threshold must be an integer.") + + @property + def split_key_method(self): + return self._split_key_method + + @split_key_method.setter + def split_key_method(self, value): + if (value is None) or (isinstance(value, enums.SplitKeyMethod)): + self._split_key_method = value + else: + raise TypeError( + "The split key method must be a SplitKeyMethod enumeration." + ) + + @property + def prime_field_size(self): + return self._prime_field_size + + @prime_field_size.setter + def prime_field_size(self, value): + if (value is None) or (isinstance(value, six.integer_types)): + self._prime_field_size = value + else: + raise TypeError("The prime field size must be an integer.") + + def __repr__(self): + cryptographic_algorithm = "cryptographic_algorithm={0}".format( + self.cryptographic_algorithm + ) + cryptographic_length = "cryptographic_length={0}".format( + self.cryptographic_length + ) + key_value = "key_value={0}".format(binascii.hexlify(self.value)) + key_format_type = "key_format_type={0}".format(self.key_format_type) + key_wrapping_data = "key_wrapping_data={0}".format( + self.key_wrapping_data + ) + cryptographic_usage_masks = "cryptographic_usage_masks={0}".format( + self.cryptographic_usage_masks + ) + names = "name={0}".format(self.names) + split_key_parts = "split_key_parts={0}".format(self.split_key_parts) + key_part_identifier = "key_part_identifier={0}".format( + self.key_part_identifier + ) + split_key_threshold = "split_key_threshold={0}".format( + self.split_key_threshold + ) + split_key_method = "split_key_method={0}".format(self.split_key_method) + prime_field_size = "prime_field_size={0}".format(self.prime_field_size) + + return "SplitKey({0})".format( + ", ".join( + [ + cryptographic_algorithm, + cryptographic_length, + key_value, + key_format_type, + key_wrapping_data, + cryptographic_usage_masks, + names, + split_key_parts, + key_part_identifier, + split_key_threshold, + split_key_method, + prime_field_size + ] + ) + ) + + def __str__(self): + return str(binascii.hexlify(self.value)) + + def __eq__(self, other): + if isinstance(other, SplitKey): + if self.value != other.value: + return False + elif self.key_format_type != other.key_format_type: + return False + elif self.cryptographic_algorithm != other.cryptographic_algorithm: + return False + elif self.cryptographic_length != other.cryptographic_length: + return False + elif self.key_wrapping_data != other.key_wrapping_data: + return False + elif self.cryptographic_usage_masks != \ + other.cryptographic_usage_masks: + return False + elif self.names != other.names: + return False + elif self.split_key_parts != other.split_key_parts: + return False + elif self.key_part_identifier != other.key_part_identifier: + return False + elif self.split_key_threshold != other.split_key_threshold: + return False + elif self.split_key_method != other.split_key_method: + return False + elif self.prime_field_size != other.prime_field_size: + return False + else: + return True + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, SplitKey): + return not (self == other) + else: + return NotImplemented + + +event.listen( + SplitKey._names, + "append", + sql.attribute_append_factory("name_index"), + retval=False +) + + class Certificate(CryptographicObject): """ The Certificate class of the simplified KMIP object hierarchy. @@ -1454,3 +1777,162 @@ event.listen(OpaqueObject._names, 'append', sql.attribute_append_factory("name_index"), retval=False) + + +class ApplicationSpecificInformation(sql.Base): + __tablename__ = "app_specific_info" + id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) + _application_namespace = sqlalchemy.Column( + "application_namespace", + sqlalchemy.String + ) + _application_data = sqlalchemy.Column( + "application_data", + sqlalchemy.String + ) + managed_objects = sqlalchemy.orm.relationship( + "ManagedObject", + secondary=app_specific_info_map, + back_populates="app_specific_info" + ) + + def __init__(self, + application_namespace=None, + application_data=None): + """ + Create an ApplicationSpecificInformation attribute. + + Args: + application_namespace (str): A string specifying the application + namespace. Required. + application_data (str): A string specifying the application data. + Required. + """ + super(ApplicationSpecificInformation, self).__init__() + + self.application_namespace = application_namespace + self.application_data = application_data + + @property + def application_namespace(self): + return self._application_namespace + + @application_namespace.setter + def application_namespace(self, value): + if (value is None) or (isinstance(value, six.string_types)): + self._application_namespace = value + else: + raise TypeError("The application namespace must be a string.") + + @property + def application_data(self): + return self._application_data + + @application_data.setter + def application_data(self, value): + if (value is None) or (isinstance(value, six.string_types)): + self._application_data = value + else: + raise TypeError("The application data must be a string.") + + def __repr__(self): + application_namespace = "application_namespace='{}'".format( + self.application_namespace + ) + application_data = "application_data='{}'".format( + self.application_data + ) + + return "ApplicationSpecificInformation({})".format( + ", ".join( + [ + application_namespace, + application_data + ] + ) + ) + + def __str__(self): + return str( + { + "application_namespace": self.application_namespace, + "application_data": self.application_data + } + ) + + def __eq__(self, other): + if isinstance(other, ApplicationSpecificInformation): + if self.application_namespace != other.application_namespace: + return False + elif self.application_data != other.application_data: + return False + else: + return True + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, ApplicationSpecificInformation): + return not (self == other) + else: + return NotImplemented + + +class ObjectGroup(sql.Base): + __tablename__ = "object_groups" + id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) + _object_group = sqlalchemy.Column( + "object_group", + sqlalchemy.String, + nullable=False + ) + managed_objects = sqlalchemy.orm.relationship( + "ManagedObject", + secondary=object_group_map, + back_populates="object_groups" + ) + + def __init__(self, object_group=None): + """ + Create an ObjectGroup attribute. + + Args: + object_group (str): A string specifying the object group. Required. + """ + super(ObjectGroup, self).__init__() + + self.object_group = object_group + + @property + def object_group(self): + return self._object_group + + @object_group.setter + def object_group(self, value): + if (value is None) or (isinstance(value, six.string_types)): + self._object_group = value + else: + raise TypeError("The object group must be a string.") + + def __repr__(self): + object_group = "object_group='{}'".format(self.object_group) + + return "ObjectGroup({})".format(object_group) + + def __str__(self): + return str({"object_group": self.object_group}) + + def __eq__(self, other): + if isinstance(other, ObjectGroup): + if self.object_group != other.object_group: + return False + else: + return True + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, ObjectGroup): + return not (self == other) + else: + return NotImplemented diff -Nru python-pykmip-0.9.1/kmip/services/kmip_client.py python-pykmip-0.10.0/kmip/services/kmip_client.py --- python-pykmip-0.9.1/kmip/services/kmip_client.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/services/kmip_client.py 2020-02-25 16:05:27.000000000 +0000 @@ -39,9 +39,12 @@ from kmip.core.enums import CredentialType from kmip.core.enums import Operation as OperationEnum +from kmip.core import exceptions + from kmip.core.factories.credentials import CredentialFactory from kmip.core import objects +from kmip.core import primitives from kmip.core.messages.contents import Authentication from kmip.core.messages.contents import BatchCount @@ -309,6 +312,120 @@ pass self.socket = None + def send_request_payload(self, operation, payload, credential=None): + """ + Send a KMIP request. + + Args: + operation (enum): An Operation enumeration specifying the type + of operation to be requested. Required. + payload (struct): A RequestPayload structure containing the + parameters for a specific KMIP operation. Required. + credential (struct): A Credential structure containing + authentication information for the server. Optional, defaults + to None. + + Returns: + response (struct): A ResponsePayload structure containing the + results of the KMIP operation specified in the request. + + Raises: + TypeError: if the payload is not a RequestPayload instance or if + the operation and payload type do not match + InvalidMessage: if the response message does not have the right + number of response payloads, or does not match the request + operation + """ + if not isinstance(payload, payloads.RequestPayload): + raise TypeError( + "The request payload must be a RequestPayload object." + ) + + # TODO (peterhamilton) For now limit this to the new Delete/Set/Modify + # Attribute operations. Migrate over existing operations to use + # this method instead. + if operation == enums.Operation.DELETE_ATTRIBUTE: + if not isinstance(payload, payloads.DeleteAttributeRequestPayload): + raise TypeError( + "The request payload for the DeleteAttribute operation " + "must be a DeleteAttributeRequestPayload object." + ) + elif operation == enums.Operation.SET_ATTRIBUTE: + if not isinstance(payload, payloads.SetAttributeRequestPayload): + raise TypeError( + "The request payload for the SetAttribute operation must " + "be a SetAttributeRequestPayload object." + ) + elif operation == enums.Operation.MODIFY_ATTRIBUTE: + if not isinstance(payload, payloads.ModifyAttributeRequestPayload): + raise TypeError( + "The request payload for the ModifyAttribute operation " + "must be a ModifyAttributeRequestPayload object." + ) + + batch_item = messages.RequestBatchItem( + operation=primitives.Enumeration( + enums.Operation, + operation, + tag=enums.Tags.OPERATION + ), + request_payload=payload + ) + + request_message = self._build_request_message(credential, [batch_item]) + response_message = self._send_and_receive_message(request_message) + + if len(response_message.batch_items) != 1: + raise exceptions.InvalidMessage( + "The response message does not have the right number of " + "requested operation results." + ) + + batch_item = response_message.batch_items[0] + + if batch_item.result_status.value != enums.ResultStatus.SUCCESS: + raise exceptions.OperationFailure( + batch_item.result_status.value, + batch_item.result_reason.value, + batch_item.result_message.value + ) + + if batch_item.operation.value != operation: + raise exceptions.InvalidMessage( + "The response message does not match the request operation." + ) + + # TODO (peterhamilton) Same as above for now. + if batch_item.operation.value == enums.Operation.DELETE_ATTRIBUTE: + if not isinstance( + batch_item.response_payload, + payloads.DeleteAttributeResponsePayload + ): + raise exceptions.InvalidMessage( + "Invalid response payload received for the " + "DeleteAttribute operation." + ) + elif batch_item.operation.value == enums.Operation.SET_ATTRIBUTE: + if not isinstance( + batch_item.response_payload, + payloads.SetAttributeResponsePayload + ): + raise exceptions.InvalidMessage( + "Invalid response payload received for the SetAttribute " + "operation." + ) + elif batch_item.operation.value == enums.Operation.MODIFY_ATTRIBUTE: + if not isinstance( + batch_item.response_payload, + payloads.ModifyAttributeResponsePayload + ): + raise exceptions.InvalidMessage( + "Invalid response payload received for the " + "ModifyAttribute operation." + ) + + return batch_item.response_payload + def create(self, object_type, template_attribute, credential=None): return self._create(object_type=object_type, template_attribute=template_attribute, @@ -694,11 +811,13 @@ return results[0] def locate(self, maximum_items=None, storage_status_mask=None, - object_group_member=None, attributes=None, credential=None): + object_group_member=None, attributes=None, credential=None, + offset_items=None): return self._locate(maximum_items=maximum_items, storage_status_mask=storage_status_mask, object_group_member=object_group_member, - attributes=attributes, credential=credential) + attributes=attributes, credential=credential, + offset_items=offset_items) def query(self, batch=False, query_functions=None, credential=None): """ @@ -1476,12 +1595,14 @@ return result def _locate(self, maximum_items=None, storage_status_mask=None, - object_group_member=None, attributes=None, credential=None): + object_group_member=None, attributes=None, credential=None, + offset_items=None): operation = Operation(OperationEnum.LOCATE) payload = payloads.LocateRequestPayload( maximum_items=maximum_items, + offset_items=offset_items, storage_status_mask=storage_status_mask, object_group_member=object_group_member, attributes=attributes diff -Nru python-pykmip-0.9.1/kmip/services/server/crypto/engine.py python-pykmip-0.10.0/kmip/services/server/crypto/engine.py --- python-pykmip-0.9.1/kmip/services/server/crypto/engine.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/services/server/crypto/engine.py 2020-02-25 16:05:27.000000000 +0000 @@ -79,14 +79,13 @@ enums.HashingAlgorithm.SHA_512: hashes.SHA512 } - # GCM is supported by cryptography but requires inputs that are not - # supported by the KMIP spec. It is excluded for now. self._modes = { enums.BlockCipherMode.CBC: modes.CBC, enums.BlockCipherMode.ECB: modes.ECB, enums.BlockCipherMode.OFB: modes.OFB, enums.BlockCipherMode.CFB: modes.CFB, - enums.BlockCipherMode.CTR: modes.CTR + enums.BlockCipherMode.CTR: modes.CTR, + enums.BlockCipherMode.GCM: modes.GCM } self._asymmetric_padding_methods = { enums.PaddingMethod.OAEP: asymmetric_padding.OAEP, @@ -294,6 +293,8 @@ cipher_mode=None, padding_method=None, iv_nonce=None, + auth_additional_data=None, + auth_tag_length=None, hashing_algorithm=None): """ Encrypt data using symmetric or asymmetric encryption. @@ -316,6 +317,12 @@ of the encryption algorithm. Optional, defaults to None. If required and not provided, it will be autogenerated and returned with the cipher text. + auth_additional_data (bytes): Any additional data to be + authenticated via the Authenticated Encryption Tag. + Optional, defaults to None. + auth_tag_length (int): The length of the authentication tag in + bytes. This parameter SHALL be provided when the Block + Cipher Mode is GCM. hashing_algorithm (HashingAlgorithm): An enumeration specifying the hashing algorithm to use with the encryption algorithm, if needed. Required for OAEP-based asymmetric encryption. @@ -328,6 +335,8 @@ * iv_nonce - the bytes of the IV/counter/nonce used if it was needed by the encryption scheme and if it was automatically generated for the encryption + * auth_tag - the bytes of the authentication tag used in GCM or + CCM mode Raises: InvalidField: Raised when the algorithm is unsupported or the @@ -373,7 +382,9 @@ plain_text, cipher_mode=cipher_mode, padding_method=padding_method, - iv_nonce=iv_nonce + iv_nonce=iv_nonce, + auth_additional_data=auth_additional_data, + auth_tag_length=auth_tag_length ) def _encrypt_symmetric( @@ -383,7 +394,9 @@ plain_text, cipher_mode=None, padding_method=None, - iv_nonce=None): + iv_nonce=None, + auth_additional_data=None, + auth_tag_length=None): """ Encrypt data using symmetric encryption. @@ -406,6 +419,12 @@ of the encryption algorithm. Optional, defaults to None. If required and not provided, it will be autogenerated and returned with the cipher text. + auth_additional_data (bytes): Any additional data to be + authenticated via the Authenticated Encryption Tag. + Optional, defaults to None. + auth_tag_length (int): The length of the authentication tag in + bytes. This parameter SHALL be provided when the Block Cipher + Mode is GCM. Returns: dict: A dictionary containing the encrypted data, with at least @@ -414,6 +433,8 @@ * iv_nonce - the bytes of the IV/counter/nonce used if it was needed by the encryption scheme and if it was automatically generated for the encryption + * auth_tag - the bytes of the authentication tag used in + GCM mode Raises: InvalidField: Raised when the algorithm is unsupported or the @@ -440,6 +461,18 @@ "Invalid key bytes for the specified encryption algorithm." ) + is_gcm_mode = cipher_mode == enums.BlockCipherMode.GCM + if not is_gcm_mode and auth_additional_data is not None: + raise exceptions.InvalidField( + 'Authenticated encryption additional data is supported ' + 'in GCM mode only.' + ) + if is_gcm_mode and auth_tag_length is None: + raise exceptions.InvalidField( + 'Authenticated encryption tag length must be provided ' + 'in GCM mode only.' + ) + # Set up the cipher mode if needed return_iv_nonce = False if encryption_algorithm == enums.CryptographicAlgorithm.RC4: @@ -459,7 +492,10 @@ if iv_nonce is None: iv_nonce = os.urandom(algorithm.block_size // 8) return_iv_nonce = True - mode = mode(iv_nonce) + if is_gcm_mode: + mode = mode(iv_nonce, None, min_tag_length=auth_tag_length) + else: + mode = mode(iv_nonce) else: mode = mode() @@ -477,15 +513,16 @@ # Encrypt the plain text cipher = ciphers.Cipher(algorithm, mode, backend=default_backend()) encryptor = cipher.encryptor() + if auth_additional_data is not None: + encryptor.authenticate_additional_data(auth_additional_data) cipher_text = encryptor.update(plain_text) + encryptor.finalize() + result = {'cipher_text': cipher_text} if return_iv_nonce: - return { - 'cipher_text': cipher_text, - 'iv_nonce': iv_nonce - } - else: - return {'cipher_text': cipher_text} + result['iv_nonce'] = iv_nonce + if is_gcm_mode: + result['auth_tag'] = encryptor.tag[:auth_tag_length] + return result def _encrypt_asymmetric(self, encryption_algorithm, @@ -612,6 +649,8 @@ cipher_mode=None, padding_method=None, iv_nonce=None, + auth_additional_data=None, + auth_tag=None, hashing_algorithm=None): """ Decrypt data using symmetric decryption. @@ -633,6 +672,14 @@ Optional otherwise, defaults to None. iv_nonce (bytes): The IV/nonce value to use to initialize the mode of the decryption algorithm. Optional, defaults to None. + auth_additional_data (bytes): Any additional data to be + authenticated via the Authenticated Encryption Tag. + Added in KMIP 1.4. + auth_tag (bytes): Specifies the tag that will be needed to + authenticate the decrypted data. Only returned on completion + of the encryption of the last of the plaintext by an + authenticated encryption cipher. Optional, defaults to None. + Added in KMIP 1.4. hashing_algorithm (HashingAlgorithm): An enumeration specifying the hashing algorithm to use with the decryption algorithm, if needed. Required for OAEP-based asymmetric decryption. @@ -687,7 +734,9 @@ cipher_text, cipher_mode=cipher_mode, padding_method=padding_method, - iv_nonce=iv_nonce + iv_nonce=iv_nonce, + auth_additional_data=auth_additional_data, + auth_tag=auth_tag ) def _decrypt_symmetric( @@ -697,7 +746,9 @@ cipher_text, cipher_mode=None, padding_method=None, - iv_nonce=None): + iv_nonce=None, + auth_additional_data=None, + auth_tag=None): """ Decrypt data using symmetric decryption. @@ -718,6 +769,14 @@ Optional otherwise, defaults to None. iv_nonce (bytes): The IV/nonce value to use to initialize the mode of the decryption algorithm. Optional, defaults to None. + auth_additional_data (bytes): Any additional data to be + authenticated via the Authenticated Encryption Tag. + Added in KMIP 1.4. + auth_tag (bytes): Specifies the tag that will be needed to + authenticate the decrypted data. Only returned on completion + of the encryption of the last of the plaintext by an + authenticated encryption cipher. Optional, defaults to None. + Added in KMIP 1.4. Returns: bytes: the bytes of the decrypted data @@ -746,6 +805,16 @@ "Invalid key bytes for the specified decryption algorithm." ) + is_gcm_mode = cipher_mode == enums.BlockCipherMode.GCM + if auth_additional_data is not None and not is_gcm_mode: + raise exceptions.InvalidField( + 'Additional data is supported in GCM mode only.' + ) + if is_gcm_mode and auth_tag is None: + raise exceptions.InvalidField( + 'Authenticated tag must be provided in GCM mode.' + ) + # Set up the cipher mode if needed if decryption_algorithm == enums.CryptographicAlgorithm.RC4: mode = None @@ -765,13 +834,22 @@ raise exceptions.InvalidField( "IV/nonce is required." ) - mode = mode(iv_nonce) + if is_gcm_mode: + mode = mode( + iv_nonce, + tag=auth_tag, + min_tag_length=len(auth_tag) + ) + else: + mode = mode(iv_nonce) else: mode = mode() # Decrypt the plain text cipher = ciphers.Cipher(algorithm, mode, backend=default_backend()) decryptor = cipher.decryptor() + if auth_additional_data is not None: + decryptor.authenticate_additional_data(auth_additional_data) plain_text = decryptor.update(cipher_text) + decryptor.finalize() # Unpad the plain text if needed (separate methods for testing @@ -797,7 +875,7 @@ padding_method, hashing_algorithm=None): """ - Encrypt data using asymmetric decryption. + Decrypt data using asymmetric decryption. Args: decryption_algorithm (CryptographicAlgorithm): An enumeration diff -Nru python-pykmip-0.9.1/kmip/services/server/engine.py python-pykmip-0.10.0/kmip/services/server/engine.py --- python-pykmip-0.9.1/kmip/services/server/engine.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/services/server/engine.py 2020-02-25 16:05:27.000000000 +0000 @@ -123,7 +123,7 @@ enums.ObjectType.SYMMETRIC_KEY: objects.SymmetricKey, enums.ObjectType.PUBLIC_KEY: objects.PublicKey, enums.ObjectType.PRIVATE_KEY: objects.PrivateKey, - enums.ObjectType.SPLIT_KEY: None, + enums.ObjectType.SPLIT_KEY: objects.SplitKey, enums.ObjectType.TEMPLATE: None, enums.ObjectType.SECRET_DATA: objects.SecretData, enums.ObjectType.OPAQUE_DATA: objects.OpaqueObject @@ -519,6 +519,19 @@ 'opaque_data_type': obj.opaque_type, 'opaque_data_value': obj.value } + elif object_type == enums.ObjectType.SPLIT_KEY: + value = { + "cryptographic_algorithm": obj.cryptographic_algorithm, + "cryptographic_length": obj.cryptographic_length, + "key_format_type": obj.key_format_type, + "key_value": obj.value, + "key_wrapping_data": obj.key_wrapping_data, + "split_key_parts": obj.split_key_parts, + "key_part_identifier": obj.key_part_identifier, + "split_key_threshold": obj.split_key_threshold, + "split_key_method": obj.split_key_method, + "prime_field_size": obj.prime_field_size + } else: name = object_type.name raise exceptions.InvalidField( @@ -652,7 +665,7 @@ names.append(name) return names elif attr_name == 'Object Type': - return managed_object._object_type + return managed_object.object_type elif attr_name == 'Cryptographic Algorithm': return managed_object.cryptographic_algorithm elif attr_name == 'Cryptographic Length': @@ -712,22 +725,122 @@ elif attr_name == 'Archive Date': return None elif attr_name == 'Object Group': - return None + return [x.object_group for x in managed_object.object_groups] elif attr_name == 'Fresh': return None elif attr_name == 'Link': return None - elif attr_name == 'Application Specific Information': - return None + elif attr_name == "Application Specific Information": + values = [] + for info in managed_object.app_specific_info: + values.append( + { + "application_namespace": info.application_namespace, + "application_data": info.application_data + } + ) + return values elif attr_name == 'Contact Information': return None elif attr_name == 'Last Change Date': return None + elif attr_name == "Sensitive": + return managed_object.sensitive else: # Since custom attribute names are possible, just return None # for unrecognized attributes. This satisfies the spec. return None + def _get_attribute_index_from_managed_object( + self, + managed_object, + attribute_name, + attribute_value + ): + """ + Find the attribute index for the specified attribute value. + + Args: + managed_object (pie.ManagedObject): A managed object kept by the + server. Usually obtained from _get_object_with_access_controls. + Required. + attribute_name (string): The name of the attribute to look up. + Required. + attribute_value (primitive.Base): A primitive object representing + the attribute value. If a simple object (e.g., Integer) just + do a direct comparison on its value. If a complex object (e.g., + Struct) do a comparison on all of the object fields. Required. + + Returns: + int - the attribute index of the attribute value on the managed + object, if it exists, 0 for single-valued attributes + None - if the attribute value could not be found on the managed + object + """ + if attribute_name == "Application Specific Information": + a = attribute_value + for count, v in enumerate(managed_object.app_specific_info): + if ((a.application_namespace == v.application_namespace) and + (a.application_data == v.application_data)): + return count + return None + elif attribute_name == "Certificate Type": + if attribute_value.value == managed_object.certificate_type: + return 0 + return None + elif attribute_name == "Cryptographic Algorithm": + if attribute_value.value == managed_object.cryptographic_algorithm: + return 0 + return None + elif attribute_name == "Cryptographic Length": + if attribute_value.value == managed_object.cryptographic_length: + return 0 + return None + elif attribute_name == "Cryptographic Usage Mask": + v = attribute_value.value + combined_mask = 0 + for mask in managed_object.cryptographic_usage_masks: + combined_mask |= mask.value + if v == combined_mask: + return 0 + return None + elif attribute_name == "Initial Date": + if attribute_value.value == managed_object.initial_date: + return 0 + return None + elif attribute_name == "Name": + for count, v in enumerate(managed_object.names): + if attribute_value.name_value.value == v: + return count + return None + elif attribute_name == "Object Group": + for count, v in enumerate(managed_object.object_groups): + if attribute_value.value == v.object_group: + return count + return None + elif attribute_name == "Object Type": + if attribute_value.value == managed_object.object_type: + return 0 + return None + elif attribute_name == "Operation Policy Name": + if attribute_value.value == managed_object.operation_policy_name: + return 0 + return None + elif attribute_name == "Sensitive": + if attribute_value.value == managed_object.sensitive: + return 0 + return None + elif attribute_name == "State": + if attribute_value.value == managed_object.state: + return 0 + return None + elif attribute_name == "Unique Identifier": + if attribute_value.value == str(managed_object.unique_identifier): + return 0 + return None + else: + return None + def _set_attributes_on_managed_object(self, managed_object, attributes): """ Given a kmip.pie object and a dictionary of attributes, attempt to set @@ -768,6 +881,21 @@ raise exceptions.InvalidField( "Cannot set duplicate name values." ) + elif attribute_name == "Application Specific Information": + for value in attribute_value: + managed_object.app_specific_info.append( + objects.ApplicationSpecificInformation( + application_namespace=value.application_namespace, + application_data=value.application_data + ) + ) + elif attribute_name == "Object Group": + for value in attribute_value: + # TODO (peterhamilton) Enforce uniqueness of object groups + # to avoid wasted space. + managed_object.object_groups.append( + objects.ObjectGroup(object_group=value.value) + ) else: # TODO (peterhamilton) Remove when all attributes are supported raise exceptions.InvalidField( @@ -789,6 +917,8 @@ value.append(e) elif attribute_name == 'Operation Policy Name': field = 'operation_policy_name' + elif attribute_name == "Sensitive": + field = "sensitive" if field: existing_value = getattr(managed_object, field) @@ -807,6 +937,118 @@ "The {0} attribute is unsupported.".format(attribute_name) ) + def _set_attribute_on_managed_object_by_index( + self, + managed_object, + attribute_name, + attribute_value, + attribute_index + ): + """ + Set the attribute value for the specified attribute index. + + Args: + managed_object (pie.ManagedObject): A managed object kept by the + server. Usually obtained from _get_object_with_access_controls. + Required. + attribute_name (string): The name of the attribute to modify. + Required. + attribute_value (primitive.Base): A primitive object representing + the new attribute value to set on the managd object. Required. + attribute_index (int): The index of the existing attribute to + modify. Required. + """ + if attribute_name == "Application Specific Information": + a = managed_object.app_specific_info[attribute_index] + a.application_namespace = attribute_value.application_namespace + a.application_data = attribute_value.application_data + elif attribute_name == "Name": + name_value = attribute_value.name_value + managed_object.names[attribute_index] = name_value.value + elif attribute_name == "Object Group": + a = managed_object.object_groups[attribute_index] + a.object_group = attribute_value.value + + def _delete_attribute_from_managed_object(self, managed_object, attribute): + attribute_name, attribute_index, attribute_value = attribute + object_type = managed_object._object_type + if not self._attribute_policy.is_attribute_applicable_to_object_type( + attribute_name, + object_type + ): + raise exceptions.ItemNotFound( + "The '{}' attribute is not applicable to '{}' objects.".format( + attribute_name, + ''.join( + [x.capitalize() for x in object_type.name.split('_')] + ) + ) + ) + if not self._attribute_policy.is_attribute_deletable_by_client( + attribute_name + ): + raise exceptions.PermissionDenied( + "Cannot delete a required attribute." + ) + + if self._attribute_policy.is_attribute_multivalued(attribute_name): + # Get the specific attribute collection and attribute objects. + attribute_list = [] + if attribute_name == "Name": + attribute_list = managed_object.names + if attribute_value is not None: + attribute_value = attribute_value.value + elif attribute_name == "Application Specific Information": + attribute_list = managed_object.app_specific_info + if attribute_value is not None: + namespace = attribute_value.application_namespace + attribute_value = objects.ApplicationSpecificInformation( + application_namespace=namespace, + application_data=attribute_value.application_data + ) + elif attribute_name == "Object Group": + attribute_list = managed_object.object_groups + if attribute_value is not None: + attribute_value = objects.ObjectGroup( + object_group=attribute_value.value + ) + else: + raise exceptions.InvalidField( + "The '{}' attribute is not supported.".format( + attribute_name + ) + ) + + # Generically handle attribute deletion. + if attribute_value: + if attribute_list.count(attribute_value): + attribute_list.remove(attribute_value) + else: + raise exceptions.ItemNotFound( + "Could not locate the attribute instance with the " + "specified value: {}".format(attribute_value) + ) + elif attribute_index is not None: + if attribute_index < len(attribute_list): + attribute_list.pop(attribute_index) + else: + raise exceptions.ItemNotFound( + "Could not locate the attribute instance with the " + "specified index: {}".format(attribute_index) + ) + else: + # If no attribute index is provided, this is not a KMIP + # 1.* request. If no attribute value is provided, this + # must be a KMIP 2.0 attribute reference request, so + # delete all instances of the attribute. + attribute_list[:] = [] + else: + # The server does not currently support any single-instance, + # client deletable attributes. + raise exceptions.InvalidField( + "The '{}' attribute is not supported.".format(attribute_name) + ) + def _is_allowed_by_operation_policy( self, policy_name, @@ -926,6 +1168,63 @@ else: return False + def _is_valid_date(self, date_type, value, start, end): + date_type = date_type.value.lower() + + if start is not None: + if end is not None: + if value < start: + self._logger.debug( + "Failed match: object's {} ({}) is less than " + "the starting {} ({}).".format( + date_type, + time.asctime(time.gmtime(value)), + date_type, + time.asctime(time.gmtime(start)) + ) + ) + return False + elif value > end: + self._logger.debug( + "Failed match: object's {} ({}) is greater than " + "the ending {} ({}).".format( + date_type, + time.asctime(time.gmtime(value)), + date_type, + time.asctime(time.gmtime(end)) + ) + ) + return False + else: + if start != value: + self._logger.debug( + "Failed match: object's {} ({}) does not match " + "the specified {} ({}).".format( + date_type, + time.asctime(time.gmtime(value)), + date_type, + time.asctime(time.gmtime(start)) + ) + ) + return False + return True + + def _track_date_attributes(self, date_type, date_values, value): + if date_values.get("start") is None: + date_values["start"] = value + elif date_values.get("end") is None: + if value > date_values.get("start"): + date_values["end"] = value + else: + date_values["end"] = date_values.get("start") + date_values["start"] = value + else: + raise exceptions.InvalidField( + "Too many {} attributes provided. " + "Include one for an exact match. " + "Include two for a ranged match.".format(date_type.value) + ) + def _get_object_with_access_controls( self, uid, @@ -978,10 +1277,13 @@ return managed_objects_allowed def _process_operation(self, operation, payload): + # TODO (peterhamilton) Alphabetize this. if operation == enums.Operation.CREATE: return self._process_create(payload) elif operation == enums.Operation.CREATE_KEY_PAIR: return self._process_create_key_pair(payload) + elif operation == enums.Operation.DELETE_ATTRIBUTE: + return self._process_delete_attribute(payload) elif operation == enums.Operation.REGISTER: return self._process_register(payload) elif operation == enums.Operation.DERIVE_KEY: @@ -1010,6 +1312,10 @@ return self._process_decrypt(payload) elif operation == enums.Operation.SIGNATURE_VERIFY: return self._process_signature_verify(payload) + elif operation == enums.Operation.SET_ATTRIBUTE: + return self._process_set_attribute(payload) + elif operation == enums.Operation.MODIFY_ATTRIBUTE: + return self._process_modify_attribute(payload) elif operation == enums.Operation.MAC: return self._process_mac(payload) elif operation == enums.Operation.SIGN: @@ -1286,6 +1592,356 @@ return response_payload @_kmip_version_supported('1.0') + def _process_delete_attribute(self, payload): + self._logger.info("Processing operation: DeleteAttribute") + + unique_identifier = self._id_placeholder + if payload.unique_identifier: + unique_identifier = payload.unique_identifier + + managed_object = self._get_object_with_access_controls( + unique_identifier, + enums.Operation.DELETE_ATTRIBUTE + ) + deleted_attribute = None + + attribute_name = None + attribute_index = None + attribute_value = None + + if self._protocol_version >= contents.ProtocolVersion(2, 0): + # If the current attribute is defined, use that. Otherwise, use + # the attribute reference. + if payload.current_attribute: + try: + attribute_name = enums.convert_attribute_tag_to_name( + payload.current_attribute.attribute.tag + ) + attribute_value = payload.current_attribute.attribute + except ValueError as e: + self._logger.exception(e) + raise exceptions.ItemNotFound( + "No attribute with the specified name exists." + ) + elif payload.attribute_reference: + attribute_name = payload.attribute_reference.attribute_name + else: + raise exceptions.InvalidMessage( + "The DeleteAttribute request must specify the current " + "attribute or an attribute reference." + ) + else: + # Build a partial attribute from the attribute name and index. + if payload.attribute_name: + attribute_name = payload.attribute_name + else: + raise exceptions.InvalidMessage( + "The DeleteAttribute request must specify the attribute " + "name." + ) + if payload.attribute_index: + attribute_index = payload.attribute_index + else: + attribute_index = 0 + + # Grab a copy of the attribute before deleting it. + existing_attributes = self._get_attributes_from_managed_object( + managed_object, + [payload.attribute_name] + ) + if len(existing_attributes) > 0: + if not attribute_index: + deleted_attribute = existing_attributes[0] + else: + if attribute_index < len(existing_attributes): + deleted_attribute = existing_attributes[ + attribute_index + ] + else: + raise exceptions.ItemNotFound( + "Could not locate the attribute instance with the " + "specified index: {}".format(attribute_index) + ) + + self._delete_attribute_from_managed_object( + managed_object, + (attribute_name, attribute_index, attribute_value) + ) + self._data_session.commit() + + response_payload = payloads.DeleteAttributeResponsePayload( + unique_identifier=unique_identifier, + attribute=deleted_attribute + ) + + return response_payload + + @_kmip_version_supported('2.0') + def _process_set_attribute(self, payload): + self._logger.info("Processing operation: SetAttribute") + + unique_identifier = self._id_placeholder + if payload.unique_identifier: + unique_identifier = payload.unique_identifier + + managed_object = self._get_object_with_access_controls( + unique_identifier, + enums.Operation.SET_ATTRIBUTE + ) + + attribute_name = enums.convert_attribute_tag_to_name( + payload.new_attribute.attribute.tag + ) + if self._attribute_policy.is_attribute_multivalued(attribute_name): + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.MULTI_VALUED_ATTRIBUTE, + message=( + "The '{}' attribute is multi-valued. Multi-valued " + "attributes cannot be set with the SetAttribute " + "operation.".format(attribute_name) + ) + ) + if not self._attribute_policy.is_attribute_modifiable_by_client( + attribute_name + ): + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.READ_ONLY_ATTRIBUTE, + message=( + "The '{}' attribute is read-only and cannot be modified " + "by the client.".format(attribute_name) + ) + ) + + self._set_attributes_on_managed_object( + managed_object, + {attribute_name: payload.new_attribute.attribute} + ) + self._data_session.commit() + + return payloads.SetAttributeResponsePayload( + unique_identifier=unique_identifier + ) + + @_kmip_version_supported('1.0') + def _process_modify_attribute(self, payload): + self._logger.info("Processing operation: ModifyAttribute") + + unique_identifier = self._id_placeholder + if payload.unique_identifier: + unique_identifier = payload.unique_identifier + + managed_object = self._get_object_with_access_controls( + unique_identifier, + enums.Operation.MODIFY_ATTRIBUTE + ) + + if self._protocol_version >= contents.ProtocolVersion(2, 0): + current_attribute = payload.current_attribute + if current_attribute: + current_attribute = current_attribute.attribute + new_attribute = payload.new_attribute.attribute + + attribute_name = enums.convert_attribute_tag_to_name( + new_attribute.tag + ) + + if not self._attribute_policy.is_attribute_modifiable_by_client( + attribute_name + ): + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.PERMISSION_DENIED, + message=( + "The '{}' attribute is read-only and cannot be " + "modified.".format(attribute_name) + ) + ) + + is_multivalued = self._attribute_policy.is_attribute_multivalued( + attribute_name + ) + + if is_multivalued: + if current_attribute is None: + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.ATTRIBUTE_INSTANCE_NOT_FOUND, + message=( + "The '{}' attribute is multivalued so the current " + "attribute must be specified.".format( + attribute_name + ) + ) + ) + else: + index = self._get_attribute_index_from_managed_object( + managed_object, + attribute_name, + current_attribute + ) + if index is None: + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.ATTRIBUTE_NOT_FOUND, + message=( + "The specified current attribute could not be " + "found on the managed object." + ) + ) + else: + self._set_attribute_on_managed_object_by_index( + managed_object, + attribute_name, + new_attribute, + index + ) + self._data_session.commit() + else: + if current_attribute is None: + # Verify the attribute is set. + existing_attr = self._get_attribute_from_managed_object( + managed_object, + attribute_name + ) + if existing_attr is None: + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.ATTRIBUTE_NOT_FOUND, + message=( + "The '{}' attribute is not set on the managed " + "object. It must be set before it can be " + "modified.".format(attribute_name) + ) + ) + else: + # Verify the attribute matches the current attribute. + index = self._get_attribute_index_from_managed_object( + managed_object, + attribute_name, + current_attribute + ) + if index is None: + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.ATTRIBUTE_NOT_FOUND, + message=( + "The specified current attribute could not be " + "found on the managed object." + ) + ) + + # Set the attribute value. + self._set_attribute_on_managed_object( + managed_object, + (attribute_name, new_attribute) + ) + self._data_session.commit() + + return payloads.ModifyAttributeResponsePayload( + unique_identifier=unique_identifier + ) + + else: + attribute_name = payload.attribute.attribute_name.value + attribute_index = payload.attribute.attribute_index + if attribute_index: + attribute_index = attribute_index.value + attribute_value = payload.attribute.attribute_value + + if not self._attribute_policy.is_attribute_modifiable_by_client( + attribute_name + ): + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.PERMISSION_DENIED, + message=( + "The '{}' attribute is read-only and cannot be " + "modified.".format(attribute_name) + ) + ) + + is_multivalued = self._attribute_policy.is_attribute_multivalued( + attribute_name + ) + + modified_attribute = None + + if is_multivalued: + if attribute_index is None: + attribute_index = 0 + + existing_attributes = self._get_attribute_from_managed_object( + managed_object, + attribute_name + ) + if 0 <= attribute_index < len(existing_attributes): + self._set_attribute_on_managed_object_by_index( + managed_object, + attribute_name, + attribute_value, + attribute_index + ) + self._data_session.commit() + else: + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.ITEM_NOT_FOUND, + message=( + "No matching attribute instance could be found " + "for the specified attribute index." + ) + ) + + existing_attributes = self._get_attributes_from_managed_object( + managed_object, + [attribute_name] + ) + modified_attribute = existing_attributes[attribute_index] + else: + if attribute_index is not None: + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.INVALID_FIELD, + message=( + "The attribute index cannot be specified for a " + "single-valued attribute." + ) + ) + existing_attribute = self._get_attributes_from_managed_object( + managed_object, + [attribute_name] + ) + if len(existing_attribute) == 0: + raise exceptions.KmipError( + status=enums.ResultStatus.OPERATION_FAILED, + reason=enums.ResultReason.INVALID_FIELD, + message=( + "The '{}' attribute is not set on the managed " + "object. It must be set before it can be " + "modified.".format(attribute_name) + ) + ) + else: + self._set_attribute_on_managed_object( + managed_object, + (attribute_name, attribute_value) + ) + self._data_session.commit() + + existing_attributes = self._get_attributes_from_managed_object( + managed_object, + [attribute_name] + ) + modified_attribute = existing_attributes[0] + + return payloads.ModifyAttributeResponsePayload( + unique_identifier=unique_identifier, + attribute=modified_attribute + ) + + @_kmip_version_supported('1.0') def _process_register(self, payload): self._logger.info("Processing operation: Register") @@ -1544,29 +2200,264 @@ managed_objects = self._list_objects_with_access_controls( enums.Operation.LOCATE) + # TODO (ph) Do a single pass on the provided attributes and preprocess + # them as needed (e.g., tracking multiple 'Initial Date' values, etc). + # Locate needs to be able to error out if multiple singleton attributes + # like 'State' are provided in the same request. if payload.attributes: managed_objects_filtered = [] # Filter the objects based on given attributes. - # TODO: Currently will only filter for 'Name'. - # Needs to add other attributes. for managed_object in managed_objects: - for attribute in payload.attributes: - attribute_name = attribute.attribute_name.value - attribute_value = attribute.attribute_value - attr = self._get_attribute_from_managed_object( - managed_object, attribute_name) - if attribute_name == 'Name': - names = attr - if attribute_value not in names: + self._logger.debug( + "Evaluating object: {}".format( + managed_object.unique_identifier + ) + ) + + add_object = True + initial_date = {} + + for payload_attribute in payload.attributes: + name = payload_attribute.attribute_name.value + value = payload_attribute.attribute_value + + # Verify that the attribute is applicable to the current + # object. If not, the object doesn't match, so skip it. + policy = self._attribute_policy + if not policy.is_attribute_applicable_to_object_type( + name, + managed_object.object_type + ): + self._logger.debug( + "Failed match: " + "the specified attribute ({}) is not applicable " + "for the object's object type ({}).".format( + name, + managed_object.object_type.name) + ) + add_object = False + break + + # Fetch the attribute from the object and check if it + # matches. If not, the object doesn't match, so skip it. + attribute = self._get_attribute_from_managed_object( + managed_object, + name + ) + if attribute is None: + continue + elif name == "Application Specific Information": + application_namespace = value.application_namespace + application_data = value.application_data + v = { + "application_namespace": application_namespace, + "application_data": application_data + } + if v not in attribute: + self._logger.debug( + "Failed match: " + "the specified application specific " + "information ('{}', '{}') does not match any " + "of the object's associated application " + "specific information attributes.".format( + v.get("application_namespace"), + v.get("application_data") + ) + ) + add_object = False break - # TODO: filtering on other attributes - else: + elif name == "Object Group": + if value.value not in attribute: + self._logger.debug( + "Failed match: " + "the specified object group ('{}') does not " + "match any of the object's associated object " + "group attributes.".format(value.value) + ) + add_object = False + break + elif name == "Name": + if value not in attribute: + self._logger.debug( + "Failed match: " + "the specified name ({}) does not match " + "any of the object's names ({}).".format( + value, + attribute + ) + ) + add_object = False + break + elif name == enums.AttributeType.STATE.value: + value = value.value + if value != attribute: + self._logger.debug( + "Failed match: " + "the specified state ({}) does not match " + "the object's state ({}).".format( + value.name, + attribute.name + ) + ) + add_object = False + break + elif name == enums.AttributeType.OBJECT_TYPE.value: + value = value.value + if value != attribute: + self._logger.debug( + "Failed match: " + "the specified object type ({}) does not " + "match the object's object type ({}).".format( + value.name, + attribute.name + ) + ) + add_object = False + break + elif name == "Cryptographic Algorithm": + value = value.value + if value != attribute: + self._logger.debug( + "Failed match: " + "the specified cryptographic algorithm ({}) " + "does not match the object's cryptographic " + "algorithm ({}).".format( + value.name, + attribute.name + ) + ) + add_object = False + break + elif name == "Cryptographic Length": + value = value.value + if value != attribute: + self._logger.debug( + "Failed match: " + "the specified cryptographic length ({}) " + "does not match the object's cryptographic " + "length ({}).".format( + value, + attribute + ) + ) + add_object = False + break + elif name == "Unique Identifier": + value = value.value + if value != attribute: + self._logger.debug( + "Failed match: " + "the specified unique identifier ({}) " + "does not match the object's unique " + "identifier ({}).".format( + value, + attribute + ) + ) + add_object = False + break + elif name == "Operation Policy Name": + value = value.value + if value != attribute: + self._logger.debug( + "Failed match: " + "the specified operation policy name ({}) " + "does not match the object's operation policy " + "name ({}).".format( + value, + attribute + ) + ) + add_object = False + break + elif name == "Cryptographic Usage Mask": + value = value.value + mask_values = enums.get_enumerations_from_bit_mask( + enums.CryptographicUsageMask, + value + ) + for mask_value in mask_values: + if mask_value not in attribute: + self._logger.debug( + "Failed match: " + "the specified cryptographic usage mask " + "({}) is not set on the object.".format( + mask_value.name + ) + ) + add_object = False + break + if not add_object: + break + elif name == "Certificate Type": + value = value.value + if value != attribute: + self._logger.debug( + "Failed match: " + "the specified certificate type ({}) " + "does not match the object's certificate " + "type ({}).".format( + value.name, + attribute.name + ) + ) + add_object = False + break + elif name == enums.AttributeType.INITIAL_DATE.value: + initial_date["value"] = attribute + self._track_date_attributes( + enums.AttributeType.INITIAL_DATE, + initial_date, + value.value + ) + else: + if value != attribute: + add_object = False + break + + if initial_date.get("value"): + add_object &= self._is_valid_date( + enums.AttributeType.INITIAL_DATE, + initial_date.get("value"), + initial_date.get("start"), + initial_date.get("end") + ) + + if add_object: + self._logger.debug( + "Locate filter matched object: {}".format( + managed_object.unique_identifier + ) + ) managed_objects_filtered.append(managed_object) managed_objects = managed_objects_filtered + # Sort the matching results by their creation date. + managed_objects = sorted( + managed_objects, + key=lambda x: x.initial_date, + reverse=True + ) + + # Skip the requested offset items and keep the requested maximum items + if payload.offset_items is not None: + if payload.maximum_items is not None: + managed_objects = managed_objects[ + payload.offset_items:( + payload.offset_items + payload.maximum_items + ) + ] + else: + managed_objects = managed_objects[payload.offset_items:] + else: + if payload.maximum_items is not None: + managed_objects = managed_objects[:payload.maximum_items] + else: + pass + unique_identifiers = [ str(x.unique_identifier) for x in managed_objects ] @@ -2059,13 +2950,16 @@ payload.data, cipher_mode=cryptographic_parameters.block_cipher_mode, padding_method=cryptographic_parameters.padding_method, - iv_nonce=payload.iv_counter_nonce + iv_nonce=payload.iv_counter_nonce, + auth_additional_data=payload.auth_additional_data, + auth_tag_length=cryptographic_parameters.tag_length ) response_payload = payloads.EncryptResponsePayload( unique_identifier, result.get('cipher_text'), - result.get('iv_nonce') + result.get('iv_nonce'), + result.get('auth_tag') ) return response_payload @@ -2122,7 +3016,9 @@ payload.data, cipher_mode=cryptographic_parameters.block_cipher_mode, padding_method=cryptographic_parameters.padding_method, - iv_nonce=payload.iv_counter_nonce + iv_nonce=payload.iv_counter_nonce, + auth_additional_data=payload.auth_additional_data, + auth_tag=payload.auth_tag ) response_payload = payloads.DecryptResponsePayload( diff -Nru python-pykmip-0.9.1/kmip/services/server/policy.py python-pykmip-0.10.0/kmip/services/server/policy.py --- python-pykmip-0.9.1/kmip/services/server/policy.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/services/server/policy.py 2020-02-25 16:05:27.000000000 +0000 @@ -129,6 +129,7 @@ """ self._version = version + # TODO (peterhamilton) Alphabetize these self._attribute_rule_sets = { 'Unique Identifier': AttributeRuleSet( True, @@ -872,10 +873,10 @@ 'Object Group': AttributeRuleSet( False, ('server', 'client'), - False, - False, - False, - False, + True, + True, + True, + True, ( enums.Operation.CREATE, enums.Operation.CREATE_KEY_PAIR, @@ -1077,6 +1078,30 @@ ), contents.ProtocolVersion(1, 0) ), + "Sensitive": AttributeRuleSet( + True, + ("server", "client"), + True, + True, + False, + False, + ( + enums.Operation.CREATE, + enums.Operation.CREATE_KEY_PAIR, + enums.Operation.REGISTER + ), + ( + enums.ObjectType.CERTIFICATE, + enums.ObjectType.SYMMETRIC_KEY, + enums.ObjectType.PUBLIC_KEY, + enums.ObjectType.PRIVATE_KEY, + enums.ObjectType.SPLIT_KEY, + enums.ObjectType.TEMPLATE, + enums.ObjectType.SECRET_DATA, + enums.ObjectType.OPAQUE_DATA + ), + contents.ProtocolVersion(1, 4) + ) } def is_attribute_supported(self, attribute): @@ -1116,6 +1141,34 @@ else: return False + def is_attribute_deletable_by_client(self, attribute): + """ + Check if the attribute can be deleted by the client. + + Args: + attribute (string): The name of the attribute (e.g., "Name"). + + Returns: + bool: True if the attribute can be deleted by the client. False + otherwise. + """ + rule_set = self._attribute_rule_sets.get(attribute) + return rule_set.deletable_by_client + + def is_attribute_modifiable_by_client(self, attribute): + """ + Check if the attribute can be modified by the client. + + Args: + attribute (string): The name of the attribute (e.g., "Name"). + + Returns: + bool: True if the attribute can be modified by the client. False + otherwise. + """ + rule_set = self._attribute_rule_sets.get(attribute) + return rule_set.modifiable_by_client + def is_attribute_applicable_to_object_type(self, attribute, object_type): """ Check if the attribute is supported by the given object type. diff -Nru python-pykmip-0.9.1/kmip/services/server/session.py python-pykmip-0.10.0/kmip/services/server/session.py --- python-pykmip-0.9.1/kmip/services/server/session.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/services/server/session.py 2020-02-25 16:05:27.000000000 +0000 @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import binascii import logging import socket import struct @@ -345,8 +346,14 @@ "does not match the advertised header length." ) else: + self._logger.debug( + "Request encoding: {}".format(binascii.hexlify(message)) + ) return message def _send_response(self, data): if len(data) > 0: + self._logger.debug( + "Response encoding: {}".format(binascii.hexlify(bytes(data))) + ) self._connection.sendall(bytes(data)) diff -Nru python-pykmip-0.9.1/kmip/tests/integration/services/test_integration.py python-pykmip-0.10.0/kmip/tests/integration/services/test_integration.py --- python-pykmip-0.9.1/kmip/tests/integration/services/test_integration.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/integration/services/test_integration.py 2020-02-25 16:05:27.000000000 +0000 @@ -14,7 +14,9 @@ # under the License. import logging -from testtools import TestCase +import pytest +import testtools +import time from kmip.core.attributes import CryptographicAlgorithm from kmip.core.attributes import CryptographicLength @@ -38,6 +40,7 @@ from kmip.core.factories.credentials import CredentialFactory from kmip.core.factories.secrets import SecretFactory +from kmip.core.messages import payloads from kmip.core.misc import KeyFormatType from kmip.core.objects import Attribute @@ -52,12 +55,11 @@ from kmip.core.secrets import Certificate from kmip.core.secrets import SecretData from kmip.core.secrets import OpaqueObject - -import pytest +from kmip.core.secrets import SplitKey @pytest.mark.usefixtures("client") -class TestIntegration(TestCase): +class TestIntegration(testtools.TestCase): def setUp(self): super(TestIntegration, self).setUp() @@ -71,6 +73,11 @@ def tearDown(self): super(TestIntegration, self).tearDown() + result = self.client.locate() + if result.result_status.value == ResultStatus.SUCCESS: + for uuid in result.uuids: + self.client.destroy(uuid=uuid) + def _create_symmetric_key(self, key_name=None): """ Helper function for creating symmetric keys. Used any time a key @@ -79,34 +86,54 @@ :return: returns the result of the "create key" operation as provided by the KMIP appliance """ - object_type = ObjectType.SYMMETRIC_KEY - attribute_type = AttributeType.CRYPTOGRAPHIC_ALGORITHM - algorithm = self.attr_factory.create_attribute(attribute_type, - CryptoAlgorithmEnum.AES) - mask_flags = [CryptographicUsageMask.ENCRYPT, - CryptographicUsageMask.DECRYPT] - attribute_type = AttributeType.CRYPTOGRAPHIC_USAGE_MASK - usage_mask = self.attr_factory.create_attribute(attribute_type, - mask_flags) - key_length = 128 - attribute_type = AttributeType.CRYPTOGRAPHIC_LENGTH - key_length_obj = self.attr_factory.create_attribute(attribute_type, - key_length) - name = Attribute.AttributeName('Name') - + cryptographic_algorithm = self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ) + cryptographic_usage_mask = self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.DECRYPT + ] + ) + cryptographic_length = self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 128 + ) if key_name is None: - key_name = 'Integration Test - Key' - - name_value = Name.NameValue(key_name) + key_name = "Integration Test - Key" + name = self.attr_factory.create_attribute( + enums.AttributeType.NAME, + key_name + ) + object_group = self.attr_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "IntegrationTestKeys" + ) + application_specific_information = self.attr_factory.create_attribute( + enums.AttributeType.APPLICATION_SPECIFIC_INFORMATION, + { + "application_namespace": "ssl", + "application_data": "www.example.com" + } + ) - name_type = Name.NameType(NameType.UNINTERPRETED_TEXT_STRING) - value = Name(name_value=name_value, name_type=name_type) - name = Attribute(attribute_name=name, attribute_value=value) - attributes = [algorithm, usage_mask, key_length_obj, name] + attributes = [ + cryptographic_algorithm, + cryptographic_usage_mask, + cryptographic_length, + name, + object_group, + application_specific_information + ] template_attribute = TemplateAttribute(attributes=attributes) - return self.client.create(object_type, template_attribute, - credential=None) + return self.client.create( + enums.ObjectType.SYMMETRIC_KEY, + template_attribute, + credential=None + ) def _create_key_pair(self, key_name=None): """ @@ -1193,3 +1220,701 @@ self.assertEqual( ResultStatus.OPERATION_FAILED, result.result_status.value) + + def test_certificate_register_locate_destroy(self): + """ + Test that newly registered certificates can be located based on their + attributes. + """ + label = "Integration Test - Register-Locate-Destroy Certificate" + value = ( + b'\x30\x82\x03\x12\x30\x82\x01\xFA\xA0\x03\x02\x01\x02\x02\x01\x01' + b'\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05\x05\x00\x30' + b'\x3B\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x0D' + b'\x30\x0B\x06\x03\x55\x04\x0A\x13\x04\x54\x45\x53\x54\x31\x0E\x30' + b'\x0C\x06\x03\x55\x04\x0B\x13\x05\x4F\x41\x53\x49\x53\x31\x0D\x30' + b'\x0B\x06\x03\x55\x04\x03\x13\x04\x4B\x4D\x49\x50\x30\x1E\x17\x0D' + b'\x31\x30\x31\x31\x30\x31\x32\x33\x35\x39\x35\x39\x5A\x17\x0D\x32' + b'\x30\x31\x31\x30\x31\x32\x33\x35\x39\x35\x39\x5A\x30\x3B\x31\x0B' + b'\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x0D\x30\x0B\x06' + b'\x03\x55\x04\x0A\x13\x04\x54\x45\x53\x54\x31\x0E\x30\x0C\x06\x03' + b'\x55\x04\x0B\x13\x05\x4F\x41\x53\x49\x53\x31\x0D\x30\x0B\x06\x03' + b'\x55\x04\x03\x13\x04\x4B\x4D\x49\x50\x30\x82\x01\x22\x30\x0D\x06' + b'\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01\x05\x00\x03\x82\x01\x0F' + b'\x00\x30\x82\x01\x0A\x02\x82\x01\x01\x00\xAB\x7F\x16\x1C\x00\x42' + b'\x49\x6C\xCD\x6C\x6D\x4D\xAD\xB9\x19\x97\x34\x35\x35\x77\x76\x00' + b'\x3A\xCF\x54\xB7\xAF\x1E\x44\x0A\xFB\x80\xB6\x4A\x87\x55\xF8\x00' + b'\x2C\xFE\xBA\x6B\x18\x45\x40\xA2\xD6\x60\x86\xD7\x46\x48\x34\x6D' + b'\x75\xB8\xD7\x18\x12\xB2\x05\x38\x7C\x0F\x65\x83\xBC\x4D\x7D\xC7' + b'\xEC\x11\x4F\x3B\x17\x6B\x79\x57\xC4\x22\xE7\xD0\x3F\xC6\x26\x7F' + b'\xA2\xA6\xF8\x9B\x9B\xEE\x9E\x60\xA1\xD7\xC2\xD8\x33\xE5\xA5\xF4' + b'\xBB\x0B\x14\x34\xF4\xE7\x95\xA4\x11\x00\xF8\xAA\x21\x49\x00\xDF' + b'\x8B\x65\x08\x9F\x98\x13\x5B\x1C\x67\xB7\x01\x67\x5A\xBD\xBC\x7D' + b'\x57\x21\xAA\xC9\xD1\x4A\x7F\x08\x1F\xCE\xC8\x0B\x64\xE8\xA0\xEC' + b'\xC8\x29\x53\x53\xC7\x95\x32\x8A\xBF\x70\xE1\xB4\x2E\x7B\xB8\xB7' + b'\xF4\xE8\xAC\x8C\x81\x0C\xDB\x66\xE3\xD2\x11\x26\xEB\xA8\xDA\x7D' + b'\x0C\xA3\x41\x42\xCB\x76\xF9\x1F\x01\x3D\xA8\x09\xE9\xC1\xB7\xAE' + b'\x64\xC5\x41\x30\xFB\xC2\x1D\x80\xE9\xC2\xCB\x06\xC5\xC8\xD7\xCC' + b'\xE8\x94\x6A\x9A\xC9\x9B\x1C\x28\x15\xC3\x61\x2A\x29\xA8\x2D\x73' + b'\xA1\xF9\x93\x74\xFE\x30\xE5\x49\x51\x66\x2A\x6E\xDA\x29\xC6\xFC' + b'\x41\x13\x35\xD5\xDC\x74\x26\xB0\xF6\x05\x02\x03\x01\x00\x01\xA3' + b'\x21\x30\x1F\x30\x1D\x06\x03\x55\x1D\x0E\x04\x16\x04\x14\x04\xE5' + b'\x7B\xD2\xC4\x31\xB2\xE8\x16\xE1\x80\xA1\x98\x23\xFA\xC8\x58\x27' + b'\x3F\x6B\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05\x05' + b'\x00\x03\x82\x01\x01\x00\xA8\x76\xAD\xBC\x6C\x8E\x0F\xF0\x17\x21' + b'\x6E\x19\x5F\xEA\x76\xBF\xF6\x1A\x56\x7C\x9A\x13\xDC\x50\xD1\x3F' + b'\xEC\x12\xA4\x27\x3C\x44\x15\x47\xCF\xAB\xCB\x5D\x61\xD9\x91\xE9' + b'\x66\x31\x9D\xF7\x2C\x0D\x41\xBA\x82\x6A\x45\x11\x2F\xF2\x60\x89' + b'\xA2\x34\x4F\x4D\x71\xCF\x7C\x92\x1B\x4B\xDF\xAE\xF1\x60\x0D\x1B' + b'\xAA\xA1\x53\x36\x05\x7E\x01\x4B\x8B\x49\x6D\x4F\xAE\x9E\x8A\x6C' + b'\x1D\xA9\xAE\xB6\xCB\xC9\x60\xCB\xF2\xFA\xE7\x7F\x58\x7E\xC4\xBB' + b'\x28\x20\x45\x33\x88\x45\xB8\x8D\xD9\xAE\xEA\x53\xE4\x82\xA3\x6E' + b'\x73\x4E\x4F\x5F\x03\xB9\xD0\xDF\xC4\xCA\xFC\x6B\xB3\x4E\xA9\x05' + b'\x3E\x52\xBD\x60\x9E\xE0\x1E\x86\xD9\xB0\x9F\xB5\x11\x20\xC1\x98' + b'\x34\xA9\x97\xB0\x9C\xE0\x8D\x79\xE8\x13\x11\x76\x2F\x97\x4B\xB1' + b'\xC8\xC0\x91\x86\xC4\xD7\x89\x33\xE0\xDB\x38\xE9\x05\x08\x48\x77' + b'\xE1\x47\xC7\x8A\xF5\x2F\xAE\x07\x19\x2F\xF1\x66\xD1\x9F\xA9\x4A' + b'\x11\xCC\x11\xB2\x7E\xD0\x50\xF7\xA2\x7F\xAE\x13\xB2\x05\xA5\x74' + b'\xC4\xEE\x00\xAA\x8B\xD6\x5D\x0D\x70\x57\xC9\x85\xC8\x39\xEF\x33' + b'\x6A\x44\x1E\xD5\x3A\x53\xC6\xB6\xB6\x96\xF1\xBD\xEB\x5F\x7E\xA8' + b'\x11\xEB\xB2\x5A\x7F\x86') + name = self.attr_factory.create_attribute( + enums.AttributeType.NAME, + label + ) + usage_mask = self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.VERIFY + ] + ) + template_attribute = TemplateAttribute(attributes=[usage_mask, name]) + + certificate = Certificate(enums.CertificateType.X_509, value) + result = self.client.register( + enums.ObjectType.CERTIFICATE, + template_attribute, + certificate + ) + uid_a = result.uuid + + self.assertEqual( + enums.ResultStatus.SUCCESS, + result.result_status.value + ) + self.assertIsInstance(uid_a, str) + + # Test locating the certificate by its "Certificate Type" value. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CERTIFICATE_TYPE, + enums.CertificateType.X_509 + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(1, len(result.uuids)) + self.assertEqual(uid_a, result.uuids[0]) + + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CERTIFICATE_TYPE, + enums.CertificateType.PGP + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(0, len(result.uuids)) + + # Clean up certificate + result = self.client.destroy(uid_a) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + result = self.client.get(uuid=result.uuid.value, credential=None) + self.assertEqual( + ResultStatus.OPERATION_FAILED, + result.result_status.value + ) + + def test_symmetric_key_create_getattributes_locate_destroy(self): + """ + Test that newly created keys can be located based on their attributes. + """ + start_time = int(time.time()) + time.sleep(2) + + key_name = "Integration Test - Create-GetAttributes-Locate-Destroy Key" + result = self._create_symmetric_key(key_name=key_name) + uid_a = result.uuid + + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(ObjectType.SYMMETRIC_KEY, result.object_type) + self.assertIsInstance(uid_a, str) + + time.sleep(2) + mid_time = int(time.time()) + time.sleep(2) + + key_name = "Integration Test - Create-GetAttributes-Locate-Destroy Key" + result = self._create_symmetric_key(key_name=key_name) + uid_b = result.uuid + + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(ObjectType.SYMMETRIC_KEY, result.object_type) + self.assertIsInstance(uid_b, str) + + time.sleep(2) + end_time = int(time.time()) + + # Get the actual "Initial Date" values for each key + result = self.client.get_attributes( + uuid=uid_a, + attribute_names=["Initial Date"] + ) + + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(1, len(result.attributes)) + self.assertEqual( + "Initial Date", + result.attributes[0].attribute_name.value + ) + initial_date_a = result.attributes[0].attribute_value.value + + result = self.client.get_attributes( + uuid=uid_b, + attribute_names=["Initial Date"] + ) + + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(1, len(result.attributes)) + self.assertEqual( + "Initial Date", + result.attributes[0].attribute_name.value + ) + initial_date_b = result.attributes[0].attribute_value.value + + # Test locating each key by its exact "Initial Date" value + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + initial_date_a + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(1, len(result.uuids)) + self.assertEqual(uid_a, result.uuids[0]) + + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + initial_date_b + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(1, len(result.uuids)) + self.assertEqual(uid_b, result.uuids[0]) + + # Test locating each key by a range around its "Initial Date" value + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + start_time + ), + self.attr_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + mid_time + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(1, len(result.uuids)) + self.assertEqual(uid_a, result.uuids[0]) + + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + mid_time + ), + self.attr_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + end_time + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(1, len(result.uuids)) + self.assertEqual(uid_b, result.uuids[0]) + + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + start_time + ), + self.attr_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + end_time + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + # Test locating each key based off of its name. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.NAME, + key_name + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + # Test locating each key based off of its state. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.STATE, + enums.State.PRE_ACTIVE + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + # Test locating each key based off of its object type. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.OBJECT_TYPE, + enums.ObjectType.SYMMETRIC_KEY + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + # Test locating each key by its cryptographic algorithm. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.IDEA + ) + ] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(0, len(result.uuids)) + + # Test locating each key by its cryptographic length. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 128 + ) + ] + ) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 2048 + ) + ] + ) + self.assertEqual(0, len(result.uuids)) + + # Test locating each key by its unique identifier. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + uid_a + ) + ] + ) + self.assertEqual(1, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + uid_b + ) + ] + ) + self.assertEqual(1, len(result.uuids)) + self.assertIn(uid_b, result.uuids) + + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + "unknown" + ) + ] + ) + self.assertEqual(0, len(result.uuids)) + + # Test locating each key by its operation policy name. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + "default" + ) + ] + ) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + "unknown" + ) + ] + ) + self.assertEqual(0, len(result.uuids)) + + # Test locating each key by its application specific information. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.APPLICATION_SPECIFIC_INFORMATION, + { + "application_namespace": "ssl", + "application_data": "www.example.com" + } + ) + ] + ) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + # Test locating each key by its object group. + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "IntegrationTestKeys" + ) + ] + ) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + # Test locating keys using offset and maximum item constraints. + result = self.client.locate(offset_items=1) + + self.assertEqual(1, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + + result = self.client.locate(maximum_items=1) + + self.assertEqual(1, len(result.uuids)) + self.assertIn(uid_b, result.uuids) + + result = self.client.locate(offset_items=1, maximum_items=1) + + self.assertEqual(1, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + + # Test locating keys using their cryptographic usage masks + mask = [enums.CryptographicUsageMask.ENCRYPT] + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + mask + ) + ] + ) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + mask.append(enums.CryptographicUsageMask.DECRYPT) + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + mask + ) + ] + ) + self.assertEqual(2, len(result.uuids)) + self.assertIn(uid_a, result.uuids) + self.assertIn(uid_b, result.uuids) + + mask.append(enums.CryptographicUsageMask.SIGN) + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + mask + ) + ] + ) + self.assertEqual(0, len(result.uuids)) + + mask = [enums.CryptographicUsageMask.EXPORT] + result = self.client.locate( + attributes=[ + self.attr_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + mask + ) + ] + ) + self.assertEqual(0, len(result.uuids)) + + # Clean up keys + result = self.client.destroy(uid_a) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + result = self.client.get(uuid=result.uuid.value, credential=None) + self.assertEqual( + ResultStatus.OPERATION_FAILED, + result.result_status.value + ) + + result = self.client.destroy(uid_b) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + result = self.client.get(uuid=result.uuid.value, credential=None) + self.assertEqual( + ResultStatus.OPERATION_FAILED, + result.result_status.value + ) + + def test_split_key_register_get_destroy(self): + """ + Tests that split keys are properly registered, retrieved, and + destroyed. + """ + usage_mask = self.attr_factory.create_attribute( + AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [CryptographicUsageMask.ENCRYPT, CryptographicUsageMask.DECRYPT] + ) + key_name = "Integration Test - Register-Get-Destroy Split Key" + name = self.attr_factory.create_attribute(AttributeType.NAME, key_name) + template_attribute = TemplateAttribute(attributes=[usage_mask, name]) + + key_data = ( + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + ) + + key_block = KeyBlock( + key_format_type=KeyFormatType(KeyFormatTypeEnum.RAW), + key_compression_type=None, + key_value=KeyValue(KeyMaterial(key_data)), + cryptographic_algorithm=CryptographicAlgorithm( + CryptoAlgorithmEnum.AES + ), + cryptographic_length=CryptographicLength(128), + key_wrapping_data=None + ) + + secret = SplitKey( + split_key_parts=3, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.XOR, + prime_field_size=None, + key_block=key_block + ) + + result = self.client.register( + ObjectType.SPLIT_KEY, + template_attribute, + secret, + credential=None + ) + + self._check_result_status(result, ResultStatus, ResultStatus.SUCCESS) + self._check_uuid(result.uuid, str) + + # Check that the returned key bytes match what was provided + uuid = result.uuid + result = self.client.get(uuid=uuid, credential=None) + + self._check_result_status(result, ResultStatus, ResultStatus.SUCCESS) + self._check_object_type( + result.object_type, + ObjectType, + ObjectType.SPLIT_KEY + ) + self._check_uuid(result.uuid, str) + + self.assertEqual(3, result.secret.split_key_parts) + self.assertEqual(1, result.secret.key_part_identifier) + self.assertEqual(2, result.secret.split_key_threshold) + self.assertEqual( + enums.SplitKeyMethod.XOR, + result.secret.split_key_method + ) + self.assertIsNone(result.secret.prime_field_size) + + # Check the secret type + self.assertIsInstance(result.secret, SplitKey) + self.assertEqual( + key_data, + result.secret.key_block.key_value.key_material.value + ) + + self.logger.debug( + 'Destroying key: ' + key_name + '\nWith UUID: ' + result.uuid + ) + + result = self.client.destroy(result.uuid) + self._check_result_status( + result, + ResultStatus, + ResultStatus.SUCCESS + ) + self._check_uuid(result.uuid.value, str) + + # Verify the secret was destroyed + result = self.client.get(uuid=uuid, credential=None) + + self._check_result_status( + result, + ResultStatus, + ResultStatus.OPERATION_FAILED + ) + self.assertIsInstance(result.result_reason.value, ResultReason) + self.assertEqual( + ResultReason.ITEM_NOT_FOUND, + result.result_reason.value + ) + + def test_modify_delete_attribute(self): + """ + Test that the KMIPProxy client can be used to set, modify, and delete + attributes for an object stored by the server. + """ + key_name = "Integration Test - Set-Modify-Delete-Attribute Key" + result = self._create_symmetric_key(key_name=key_name) + key_uuid = result.uuid + + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(ObjectType.SYMMETRIC_KEY, result.object_type) + self.assertIsInstance(key_uuid, str) + + # Get the "Name" attribute for the key + result = self.client.get_attributes( + uuid=key_uuid, + attribute_names=["Name"] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(1, len(result.attributes)) + self.assertEqual( + "Name", + result.attributes[0].attribute_name.value + ) + self.assertEqual(0, result.attributes[0].attribute_index.value) + self.assertEqual( + "Integration Test - Set-Modify-Delete-Attribute Key", + result.attributes[0].attribute_value.name_value.value + ) + + # Modify the "Name" attribute for the key. + response_payload = self.client.send_request_payload( + enums.Operation.MODIFY_ATTRIBUTE, + payloads.ModifyAttributeRequestPayload( + unique_identifier=key_uuid, + attribute=self.attr_factory.create_attribute( + enums.AttributeType.NAME, + "New Name", + index=0 + ) + ) + ) + self.assertEqual(key_uuid, response_payload.unique_identifier) + self.assertEqual( + "Name", + response_payload.attribute.attribute_name.value + ) + self.assertEqual(0, response_payload.attribute.attribute_index.value) + self.assertEqual( + "New Name", + response_payload.attribute.attribute_value.name_value.value + ) + + # Get the "Name" attribute for the key to verify it was modified + result = self.client.get_attributes( + uuid=key_uuid, + attribute_names=["Name"] + ) + self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) + self.assertEqual(1, len(result.attributes)) + self.assertEqual( + "Name", + result.attributes[0].attribute_name.value + ) + self.assertEqual(0, result.attributes[0].attribute_index.value) + self.assertEqual( + "New Name", + result.attributes[0].attribute_value.name_value.value + ) + + # Delete the "Name" attribute for the key. + response_payload = self.client.send_request_payload( + enums.Operation.DELETE_ATTRIBUTE, + payloads.DeleteAttributeRequestPayload( + unique_identifier=key_uuid, + attribute_name="Name", + attribute_index=0 + ) + ) + self.assertEqual(key_uuid, response_payload.unique_identifier) + self.assertEqual( + "Name", + response_payload.attribute.attribute_name.value + ) + self.assertEqual(0, response_payload.attribute.attribute_index.value) + self.assertEqual( + "New Name", + response_payload.attribute.attribute_value.name_value.value + ) diff -Nru python-pykmip-0.9.1/kmip/tests/integration/services/test_proxykmipclient.py python-pykmip-0.10.0/kmip/tests/integration/services/test_proxykmipclient.py --- python-pykmip-0.9.1/kmip/tests/integration/services/test_proxykmipclient.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/integration/services/test_proxykmipclient.py 2020-02-25 16:05:27.000000000 +0000 @@ -15,9 +15,11 @@ import six import testtools +import time import pytest from kmip.core import enums +from kmip.core.factories import attributes as attribute_factory from kmip.pie import exceptions from kmip.pie import factory @@ -30,10 +32,15 @@ def setUp(self): super(TestProxyKmipClientIntegration, self).setUp() self.object_factory = factory.ObjectFactory() + self.attribute_factory = attribute_factory.AttributeFactory() def tearDown(self): super(TestProxyKmipClientIntegration, self).tearDown() + uuids = self.client.locate() + for uuid in uuids: + self.client.destroy(uid=uuid) + def test_symmetric_key_create_get_destroy(self): """ Test that the ProxyKmipClient can create, retrieve, and destroy a @@ -860,3 +867,552 @@ ) self.client.destroy(public_key_id) self.client.destroy(private_key_id) + + def test_certificate_register_locate_destroy(self): + """ + Test that newly registered certificates can be located based on their + attributes. + """ + label = "Integration Test - Register-Locate-Destroy Certificate" + value = ( + b'\x30\x82\x03\x12\x30\x82\x01\xFA\xA0\x03\x02\x01\x02\x02\x01\x01' + b'\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05\x05\x00\x30' + b'\x3B\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x0D' + b'\x30\x0B\x06\x03\x55\x04\x0A\x13\x04\x54\x45\x53\x54\x31\x0E\x30' + b'\x0C\x06\x03\x55\x04\x0B\x13\x05\x4F\x41\x53\x49\x53\x31\x0D\x30' + b'\x0B\x06\x03\x55\x04\x03\x13\x04\x4B\x4D\x49\x50\x30\x1E\x17\x0D' + b'\x31\x30\x31\x31\x30\x31\x32\x33\x35\x39\x35\x39\x5A\x17\x0D\x32' + b'\x30\x31\x31\x30\x31\x32\x33\x35\x39\x35\x39\x5A\x30\x3B\x31\x0B' + b'\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x0D\x30\x0B\x06' + b'\x03\x55\x04\x0A\x13\x04\x54\x45\x53\x54\x31\x0E\x30\x0C\x06\x03' + b'\x55\x04\x0B\x13\x05\x4F\x41\x53\x49\x53\x31\x0D\x30\x0B\x06\x03' + b'\x55\x04\x03\x13\x04\x4B\x4D\x49\x50\x30\x82\x01\x22\x30\x0D\x06' + b'\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01\x05\x00\x03\x82\x01\x0F' + b'\x00\x30\x82\x01\x0A\x02\x82\x01\x01\x00\xAB\x7F\x16\x1C\x00\x42' + b'\x49\x6C\xCD\x6C\x6D\x4D\xAD\xB9\x19\x97\x34\x35\x35\x77\x76\x00' + b'\x3A\xCF\x54\xB7\xAF\x1E\x44\x0A\xFB\x80\xB6\x4A\x87\x55\xF8\x00' + b'\x2C\xFE\xBA\x6B\x18\x45\x40\xA2\xD6\x60\x86\xD7\x46\x48\x34\x6D' + b'\x75\xB8\xD7\x18\x12\xB2\x05\x38\x7C\x0F\x65\x83\xBC\x4D\x7D\xC7' + b'\xEC\x11\x4F\x3B\x17\x6B\x79\x57\xC4\x22\xE7\xD0\x3F\xC6\x26\x7F' + b'\xA2\xA6\xF8\x9B\x9B\xEE\x9E\x60\xA1\xD7\xC2\xD8\x33\xE5\xA5\xF4' + b'\xBB\x0B\x14\x34\xF4\xE7\x95\xA4\x11\x00\xF8\xAA\x21\x49\x00\xDF' + b'\x8B\x65\x08\x9F\x98\x13\x5B\x1C\x67\xB7\x01\x67\x5A\xBD\xBC\x7D' + b'\x57\x21\xAA\xC9\xD1\x4A\x7F\x08\x1F\xCE\xC8\x0B\x64\xE8\xA0\xEC' + b'\xC8\x29\x53\x53\xC7\x95\x32\x8A\xBF\x70\xE1\xB4\x2E\x7B\xB8\xB7' + b'\xF4\xE8\xAC\x8C\x81\x0C\xDB\x66\xE3\xD2\x11\x26\xEB\xA8\xDA\x7D' + b'\x0C\xA3\x41\x42\xCB\x76\xF9\x1F\x01\x3D\xA8\x09\xE9\xC1\xB7\xAE' + b'\x64\xC5\x41\x30\xFB\xC2\x1D\x80\xE9\xC2\xCB\x06\xC5\xC8\xD7\xCC' + b'\xE8\x94\x6A\x9A\xC9\x9B\x1C\x28\x15\xC3\x61\x2A\x29\xA8\x2D\x73' + b'\xA1\xF9\x93\x74\xFE\x30\xE5\x49\x51\x66\x2A\x6E\xDA\x29\xC6\xFC' + b'\x41\x13\x35\xD5\xDC\x74\x26\xB0\xF6\x05\x02\x03\x01\x00\x01\xA3' + b'\x21\x30\x1F\x30\x1D\x06\x03\x55\x1D\x0E\x04\x16\x04\x14\x04\xE5' + b'\x7B\xD2\xC4\x31\xB2\xE8\x16\xE1\x80\xA1\x98\x23\xFA\xC8\x58\x27' + b'\x3F\x6B\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05\x05' + b'\x00\x03\x82\x01\x01\x00\xA8\x76\xAD\xBC\x6C\x8E\x0F\xF0\x17\x21' + b'\x6E\x19\x5F\xEA\x76\xBF\xF6\x1A\x56\x7C\x9A\x13\xDC\x50\xD1\x3F' + b'\xEC\x12\xA4\x27\x3C\x44\x15\x47\xCF\xAB\xCB\x5D\x61\xD9\x91\xE9' + b'\x66\x31\x9D\xF7\x2C\x0D\x41\xBA\x82\x6A\x45\x11\x2F\xF2\x60\x89' + b'\xA2\x34\x4F\x4D\x71\xCF\x7C\x92\x1B\x4B\xDF\xAE\xF1\x60\x0D\x1B' + b'\xAA\xA1\x53\x36\x05\x7E\x01\x4B\x8B\x49\x6D\x4F\xAE\x9E\x8A\x6C' + b'\x1D\xA9\xAE\xB6\xCB\xC9\x60\xCB\xF2\xFA\xE7\x7F\x58\x7E\xC4\xBB' + b'\x28\x20\x45\x33\x88\x45\xB8\x8D\xD9\xAE\xEA\x53\xE4\x82\xA3\x6E' + b'\x73\x4E\x4F\x5F\x03\xB9\xD0\xDF\xC4\xCA\xFC\x6B\xB3\x4E\xA9\x05' + b'\x3E\x52\xBD\x60\x9E\xE0\x1E\x86\xD9\xB0\x9F\xB5\x11\x20\xC1\x98' + b'\x34\xA9\x97\xB0\x9C\xE0\x8D\x79\xE8\x13\x11\x76\x2F\x97\x4B\xB1' + b'\xC8\xC0\x91\x86\xC4\xD7\x89\x33\xE0\xDB\x38\xE9\x05\x08\x48\x77' + b'\xE1\x47\xC7\x8A\xF5\x2F\xAE\x07\x19\x2F\xF1\x66\xD1\x9F\xA9\x4A' + b'\x11\xCC\x11\xB2\x7E\xD0\x50\xF7\xA2\x7F\xAE\x13\xB2\x05\xA5\x74' + b'\xC4\xEE\x00\xAA\x8B\xD6\x5D\x0D\x70\x57\xC9\x85\xC8\x39\xEF\x33' + b'\x6A\x44\x1E\xD5\x3A\x53\xC6\xB6\xB6\x96\xF1\xBD\xEB\x5F\x7E\xA8' + b'\x11\xEB\xB2\x5A\x7F\x86') + usage_mask = [ + enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.VERIFY + ] + + certificate = objects.Certificate( + enums.CertificateType.X_509, + value, + masks=usage_mask, + name=label + ) + a_id = self.client.register(certificate) + + # Test locating the certificate by its "Certificate Type" value. + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CERTIFICATE_TYPE, + enums.CertificateType.X_509 + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertEqual(a_id, result[0]) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CERTIFICATE_TYPE, + enums.CertificateType.PGP + ) + ] + ) + self.assertEqual(0, len(result)) + + # Clean up the certificate + self.client.destroy(a_id) + + def test_create_getattributes_locate_destroy(self): + """ + Test that the ProxyKmipClient can create symmetric keys and then + locate those keys using their attributes. + """ + start_time = int(time.time()) + time.sleep(2) + + # Create some symmetric keys + a_id = self.client.create(enums.CryptographicAlgorithm.AES, 256) + + time.sleep(2) + mid_time = int(time.time()) + time.sleep(2) + + b_id = self.client.create(enums.CryptographicAlgorithm.IDEA, 128) + + time.sleep(2) + end_time = int(time.time()) + + self.assertIsInstance(a_id, str) + self.assertIsInstance(b_id, str) + + # Get the "Initial Date" attributes for each key + result_id, result_attributes = self.client.get_attributes( + uid=a_id, + attribute_names=["Initial Date"] + ) + self.assertEqual(1, len(result_attributes)) + self.assertEqual( + "Initial Date", + result_attributes[0].attribute_name.value + ) + initial_date_a = result_attributes[0].attribute_value.value + + result_id, result_attributes = self.client.get_attributes( + uid=b_id, + attribute_names=["Initial Date"] + ) + self.assertEqual(1, len(result_attributes)) + self.assertEqual( + "Initial Date", + result_attributes[0].attribute_name.value + ) + initial_date_b = result_attributes[0].attribute_value.value + + # Test locating each key by its exact "Initial Date" value + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + initial_date_a + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertEqual(a_id, result[0]) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + initial_date_b + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertEqual(b_id, result[0]) + + # Test locating each key by a range around its "Initial Date" value + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + start_time + ), + self.attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + mid_time + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertEqual(a_id, result[0]) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + mid_time + ), + self.attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + end_time + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertEqual(b_id, result[0]) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + start_time + ), + self.attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + end_time + ) + ] + ) + self.assertEqual(2, len(result)) + self.assertIn(a_id, result) + self.assertIn(b_id, result) + + # Test locating each key by its state. + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.STATE, + enums.State.PRE_ACTIVE + ) + ] + ) + self.assertEqual(2, len(result)) + self.assertIn(a_id, result) + self.assertIn(b_id, result) + + # Test locating each key by its object type. + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.OBJECT_TYPE, + enums.ObjectType.SYMMETRIC_KEY + ) + ] + ) + self.assertEqual(2, len(result)) + self.assertIn(a_id, result) + self.assertIn(b_id, result) + + # Test locating each key by its cryptographic algorithm. + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertIn(a_id, result) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.IDEA + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertIn(b_id, result) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.RSA + ) + ] + ) + self.assertEqual(0, len(result)) + + # Test locating each key by its cryptographic length. + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 128 + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertIn(b_id, result) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 256 + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertIn(a_id, result) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 2048 + ) + ] + ) + self.assertEqual(0, len(result)) + + # Test locating each key by its unique identifier. + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + a_id + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertIn(a_id, result) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + b_id + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertIn(b_id, result) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + "unknown" + ) + ] + ) + self.assertEqual(0, len(result)) + + # Test locating each key by its operation policy name. + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + "default" + ) + ] + ) + self.assertEqual(2, len(result)) + self.assertIn(a_id, result) + self.assertIn(b_id, result) + + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + "unknown" + ) + ] + ) + self.assertEqual(0, len(result)) + + # Test locating keys using offset and maximum item constraints. + result = self.client.locate(offset_items=1) + + self.assertEqual(1, len(result)) + self.assertIn(a_id, result) + + result = self.client.locate(maximum_items=1) + + self.assertEqual(1, len(result)) + self.assertIn(b_id, result) + + result = self.client.locate(offset_items=1, maximum_items=1) + + self.assertEqual(1, len(result)) + self.assertIn(a_id, result) + + # Test locating keys using their cryptographic usage masks + mask = [enums.CryptographicUsageMask.ENCRYPT] + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + mask + ) + ] + ) + self.assertEqual(2, len(result)) + self.assertIn(a_id, result) + self.assertIn(b_id, result) + + mask.append(enums.CryptographicUsageMask.DECRYPT) + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + mask + ) + ] + ) + self.assertEqual(2, len(result)) + self.assertIn(a_id, result) + self.assertIn(b_id, result) + + mask.append(enums.CryptographicUsageMask.SIGN) + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + mask + ) + ] + ) + self.assertEqual(0, len(result)) + + mask = [enums.CryptographicUsageMask.EXPORT] + result = self.client.locate( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + mask + ) + ] + ) + self.assertEqual(0, len(result)) + + # Clean up the keys + self.client.destroy(a_id) + self.client.destroy(b_id) + + def test_split_key_register_get_destroy(self): + """ + Test that the ProxyKmipClient can register, retrieve, and destroy a + split key. + """ + key = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + ), + name="Test Split Key", + cryptographic_usage_masks=[enums.CryptographicUsageMask.EXPORT], + key_format_type=enums.KeyFormatType.RAW, + key_wrapping_data=None, + split_key_parts=3, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.XOR, + prime_field_size=None + ) + + uid = self.client.register(key) + self.assertIsInstance(uid, six.string_types) + + try: + result = self.client.get(uid) + self.assertIsInstance(result, objects.SplitKey) + self.assertEqual( + enums.CryptographicAlgorithm.AES, + result.cryptographic_algorithm + ) + self.assertEqual(128, result.cryptographic_length) + self.assertEqual( + ( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + ), + result.value + ) + self.assertEqual(enums.KeyFormatType.RAW, result.key_format_type) + self.assertEqual(3, result.split_key_parts) + self.assertEqual(1, result.key_part_identifier) + self.assertEqual(2, result.split_key_threshold) + self.assertEqual(enums.SplitKeyMethod.XOR, result.split_key_method) + self.assertIsNone(result.prime_field_size) + finally: + self.client.destroy(uid) + self.assertRaises( + exceptions.KmipOperationFailure, self.client.get, uid) + self.assertRaises( + exceptions.KmipOperationFailure, self.client.destroy, uid) + + def test_modify_delete_attribute(self): + """ + Test that the ProxyKmipClient can modify and delete an attribute. + """ + key_id = self.client.create( + enums.CryptographicAlgorithm.IDEA, + 128, + name="Symmetric Key" + ) + + self.assertIsInstance(key_id, str) + + # Get the "Name" attribute for the key. + result_id, result_attributes = self.client.get_attributes( + uid=key_id, + attribute_names=["Name"] + ) + self.assertEqual(1, len(result_attributes)) + self.assertEqual("Name", result_attributes[0].attribute_name.value) + self.assertEqual( + "Symmetric Key", + result_attributes[0].attribute_value.name_value.value + ) + + # Modify the "Name" attribute for the key. + response_id, response_attr = self.client.modify_attribute( + unique_identifier=key_id, + attribute=self.attribute_factory.create_attribute( + enums.AttributeType.NAME, + "Modified Name", + index=0 + ) + ) + self.assertEqual(key_id, response_id) + self.assertEqual("Name", response_attr.attribute_name.value) + self.assertEqual(0, response_attr.attribute_index.value) + self.assertEqual( + "Modified Name", + response_attr.attribute_value.name_value.value + ) + + # Get the "Name" attribute for the key to verify it was modified. + result_id, result_attributes = self.client.get_attributes( + uid=key_id, + attribute_names=["Name"] + ) + self.assertEqual(1, len(result_attributes)) + self.assertEqual("Name", result_attributes[0].attribute_name.value) + self.assertEqual( + "Modified Name", + result_attributes[0].attribute_value.name_value.value + ) + + # Delete the "Name" attribute for the key. + response_id, response_attr = self.client.delete_attribute( + unique_identifier=key_id, + attribute_name="Name", + attribute_index=0 + ) + self.assertEqual(key_id, response_id) + self.assertEqual("Name", response_attr.attribute_name.value) + self.assertEqual(0, response_attr.attribute_index.value) + self.assertEqual( + "Modified Name", + response_attr.attribute_value.name_value.value + ) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/attributes/test_application_specific_information.py python-pykmip-0.10.0/kmip/tests/unit/core/attributes/test_application_specific_information.py --- python-pykmip-0.9.1/kmip/tests/unit/core/attributes/test_application_specific_information.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/attributes/test_application_specific_information.py 2020-02-25 16:05:27.000000000 +0000 @@ -13,16 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. -from testtools import TestCase +import testtools -from kmip.core.attributes import ApplicationData -from kmip.core.attributes import ApplicationNamespace -from kmip.core.attributes import ApplicationSpecificInformation +from kmip.core import attributes +from kmip.core import exceptions +from kmip.core import utils -from kmip.core.utils import BytearrayStream - -class TestApplicationSpecificInformation(TestCase): +class TestApplicationSpecificInformation(testtools.TestCase): """ A test suite for the ApplicationSpecificInformation class. """ @@ -30,364 +28,338 @@ def setUp(self): super(TestApplicationSpecificInformation, self).setUp() - self.encoding_default = BytearrayStream(( - b'\x42\x00\x04\x01\x00\x00\x00\x10\x42\x00\x03\x07\x00\x00\x00\x00' - b'\x42\x00\x02\x07\x00\x00\x00\x00')) - self.encoding = BytearrayStream(( - b'\x42\x00\x04\x01\x00\x00\x00\x28\x42\x00\x03\x07\x00\x00\x00\x03' - b'\x73\x73\x6C\x00\x00\x00\x00\x00\x42\x00\x02\x07\x00\x00\x00\x0F' - b'\x77\x77\x77\x2E\x65\x78\x61\x6D\x70\x6C\x65\x2E\x63\x6F\x6D' - b'\x00')) + # This encoding was taken from test case 3.1.2 from the KMIP 1.1 test + # document. + # + # This encoding matches the following set of values: + # Application Specific Information + # Application Namespace - ssl + # Application Data - www.example.com + self.full_encoding = utils.BytearrayStream( + b'\x42\x00\x04\x01\x00\x00\x00\x28' + b'\x42\x00\x03\x07\x00\x00\x00\x03\x73\x73\x6C\x00\x00\x00\x00\x00' + b'\x42\x00\x02\x07\x00\x00\x00\x0F' + b'\x77\x77\x77\x2E\x65\x78\x61\x6D\x70\x6C\x65\x2E\x63\x6F\x6D\x00' + ) + + # This encoding was adapted from test case 3.1.2 from the KMIP 1.1 test + # document. + # + # This encoding matches the following set of values: + # Application Specific Information + # Application Data - www.example.com + self.no_application_namespace_encoding = utils.BytearrayStream( + b'\x42\x00\x04\x01\x00\x00\x00\x18' + b'\x42\x00\x02\x07\x00\x00\x00\x0F' + b'\x77\x77\x77\x2E\x65\x78\x61\x6D\x70\x6C\x65\x2E\x63\x6F\x6D\x00' + ) + + # This encoding was adapted from test case 3.1.2 from the KMIP 1.1 test + # document. + # + # This encoding matches the following set of values: + # Application Specific Information + # Application Namespace - ssl + self.no_application_data_encoding = utils.BytearrayStream( + b'\x42\x00\x04\x01\x00\x00\x00\x10' + b'\x42\x00\x03\x07\x00\x00\x00\x03\x73\x73\x6C\x00\x00\x00\x00\x00' + ) def tearDown(self): super(TestApplicationSpecificInformation, self).tearDown() - def _test_init(self, application_namespace, application_data): - application_specific_information = ApplicationSpecificInformation( - application_namespace=application_namespace, - application_data=application_data) - - if application_namespace is None: - self.assertEqual( - ApplicationNamespace(), - application_specific_information.application_namespace) - else: - self.assertEqual( - application_namespace, - application_specific_information.application_namespace) - - if application_data is None: - self.assertEqual( - ApplicationData(), - application_specific_information.application_data) - else: - self.assertEqual( - application_data, - application_specific_information.application_data) - - def test_init_with_none(self): - """ - Test that an ApplicationSpecificInformation object can be constructed - with no specified values. - """ - self._test_init(None, None) - - def test_init_with_args(self): - """ - Test that an ApplicationSpecificInformation object can be constructed - with valid values. - """ - application_namespace = ApplicationNamespace("namespace") - application_data = ApplicationData("data") - self._test_init(application_namespace, application_data) - - def test_validate_on_invalid_application_namespace(self): - """ - Test that a TypeError exception is raised when an invalid - ApplicationNamespace value is used to construct an - ApplicationSpecificInformation object. - """ - application_namespace = "invalid" - application_data = ApplicationData() - args = [application_namespace, application_data] - - self.assertRaisesRegex( - TypeError, "invalid application namespace", - ApplicationSpecificInformation, *args) - - def test_validate_on_invalid_application_data(self): + def test_init(self): + """ + Test that an ApplicationSpecificInformation object can be constructed. """ - Test that a TypeError exception is raised when an invalid - ApplicationData value is used to construct an - ApplicationSpecificInformation object. - """ - application_namespace = ApplicationNamespace() - application_data = "invalid" - args = [application_namespace, application_data] + app_specific_info = attributes.ApplicationSpecificInformation() - self.assertRaisesRegex( - TypeError, "invalid application data", - ApplicationSpecificInformation, *args) + self.assertIsNone(app_specific_info.application_namespace) + self.assertIsNone(app_specific_info.application_data) - def _test_read(self, stream, application_namespace, application_data): - application_specific_information = ApplicationSpecificInformation() - application_specific_information.read(stream) - - if application_namespace is None: - application_namespace = ApplicationNamespace() - if application_data is None: - application_data = ApplicationData() - - msg = "application namespace encoding mismatch" - msg += "; expected {0}, observed {1}".format( - application_namespace, - application_specific_information.application_namespace) - self.assertEqual( - application_namespace, - application_specific_information.application_namespace, msg) + app_specific_info = attributes.ApplicationSpecificInformation( + application_namespace="namespace", + application_data="data" + ) - msg = "application data encoding mismatch" - msg += "; expected {0}, observed {1}".format( - application_data, - application_specific_information.application_data) - self.assertEqual( - application_data, - application_specific_information.application_data, msg) + self.assertEqual("namespace", app_specific_info.application_namespace) + self.assertEqual("data", app_specific_info.application_data) - def test_read_with_none(self): + def test_invalid_application_namespace(self): """ - Test that an ApplicationSpecificInformation object with no data can be - read from a data stream. + Test that a TypeError is raised when an invalid value is used to set + the application namespace of an ApplicationSpecificInformation object. """ - self._test_read(self.encoding_default, None, None) + kwargs = {"application_namespace": []} + self.assertRaisesRegex( + TypeError, + "The application namespace must be a string.", + attributes.ApplicationSpecificInformation, + **kwargs + ) - def test_read_with_args(self): + args = ( + attributes.ApplicationSpecificInformation(), + "application_namespace", + [] + ) + self.assertRaisesRegex( + TypeError, + "The application namespace must be a string.", + setattr, + *args + ) + + def test_invalid_application_data(self): """ - Test that an ApplicationSpecificInformation object with data can be - read from a data stream. + Test that a TypeError is raised when an invalid value is used to set + the application data of an ApplicationSpecificInformation object. """ - application_namespace = ApplicationNamespace("ssl") - application_data = ApplicationData("www.example.com") - self._test_read(self.encoding, application_namespace, application_data) + kwargs = {"application_data": []} + self.assertRaisesRegex( + TypeError, + "The application data must be a string.", + attributes.ApplicationSpecificInformation, + **kwargs + ) - def _test_write(self, stream_expected, application_namespace, - application_data): - stream_observed = BytearrayStream() - application_specific_information = ApplicationSpecificInformation( - application_namespace=application_namespace, - application_data=application_data) - application_specific_information.write(stream_observed) + args = ( + attributes.ApplicationSpecificInformation(), + "application_data", + [] + ) + self.assertRaisesRegex( + TypeError, + "The application data must be a string.", + setattr, + *args + ) + + def test_read(self): + """ + Test that an ApplicationSpecificInformation object can be read from a + buffer. + """ + app_specific_info = attributes.ApplicationSpecificInformation() - length_expected = len(stream_expected) - length_observed = len(stream_observed) + self.assertIsNone(app_specific_info.application_namespace) + self.assertIsNone(app_specific_info.application_data) - msg = "encoding lengths not equal" - msg += "; expected {0}, observed {1}".format( - length_expected, length_observed) - self.assertEqual(length_expected, length_observed, msg) + app_specific_info.read(self.full_encoding) - msg = "encoding mismatch" - msg += ";\nexpected:\n{0}\nobserved:\n{1}".format( - stream_expected, stream_observed) - self.assertEqual(stream_expected, stream_observed, msg) + self.assertEqual("ssl", app_specific_info.application_namespace) + self.assertEqual("www.example.com", app_specific_info.application_data) - def test_write_with_none(self): + def test_read_missing_application_namespace(self): """ - Test that an ApplicationSpecificInformation object with no data can be - written to a data stream. + Test that an InvalidKmipEncoding error is raised during the decoding of + an ApplicationSpecificInformation object with the application namespace + is missing from the encoding. """ - self._test_write(self.encoding_default, None, None) + app_specific_info = attributes.ApplicationSpecificInformation() - def test_write_with_args(self): - """ - Test that an ApplicationSpecificInformation object with data can be - written to a data stream. - """ - application_namespace = ApplicationNamespace("ssl") - application_data = ApplicationData("www.example.com") - self._test_write(self.encoding, application_namespace, - application_data) + self.assertIsNone(app_specific_info.application_namespace) - def test_repr(self): + args = (self.no_application_namespace_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The ApplicationSpecificInformation encoding is missing the " + "ApplicationNamespace field.", + app_specific_info.read, + *args + ) + + def test_read_missing_application_data(self): """ - Test that an ApplicationSpecificInformation object can be represented - using repr correctly. + Test that an InvalidKmipEncoding error is raised during the decoding of + an ApplicationSpecificInformation object with the application data is + missing from the encoding. """ - application_specific_info = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace("ssl"), - application_data=ApplicationData("www.example.com") - ) - s = repr(application_specific_info) + app_specific_info = attributes.ApplicationSpecificInformation() - self.assertEqual( - "ApplicationSpecificInformation(" - "application_namespace=ApplicationNamespace(value='ssl'), " - "application_data=ApplicationData(value='www.example.com'))", - s + self.assertIsNone(app_specific_info.application_data) + + args = (self.no_application_data_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The ApplicationSpecificInformation encoding is missing the " + "ApplicationData field.", + app_specific_info.read, + *args ) - def test_str(self): + def test_write(self): """ - Test that an ApplicationSpecificInformation object can be turned into - a string correctly. + Test that an ApplicationSpecificInformation object can be written to a + buffer. """ - application_specific_info = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace("ssl"), - application_data=ApplicationData("www.example.com") + app_specific_info = attributes.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" ) - s = str(application_specific_info) - self.assertEqual( - str({'application_namespace': 'ssl', - 'application_data': 'www.example.com'} - ), - s - ) + buff = utils.BytearrayStream() + app_specific_info.write(buff) + + self.assertEqual(len(self.full_encoding), len(buff)) + self.assertEqual(str(self.full_encoding), str(buff)) - def test_equal_on_equal(self): + def test_write_missing_application_namespace(self): """ - Test that the equality operator returns True when comparing two - ApplicationSpecificInformation objects with the same data. + Test that an InvalidField error is raised during the encoding of an + ApplicationSpecificInformation object when the object is missing the + application namespace field. """ - a = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data') - ) - b = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data') + app_specific_info = attributes.ApplicationSpecificInformation( + application_data="www.example.com" ) - self.assertTrue(a == b) - self.assertTrue(b == a) + buff = utils.BytearrayStream() + args = (buff, ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The ApplicationSpecificInformation object is missing the " + "ApplicationNamespace field.", + app_specific_info.write, + *args + ) - def test_equal_on_not_equal_namespace(self): + def test_write_missing_application_data(self): """ - Test that the equality operator returns False when comparing two - ApplicationSpecificInformation objects with different data. + Test that an InvalidField error is raised during the encoding of an + ApplicationSpecificInformation object when the object is missing the + application data field. """ - a = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace_1'), - application_data=ApplicationData('test_data') - ) - b = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace_2'), - application_data=ApplicationData('test_data') + app_specific_info = attributes.ApplicationSpecificInformation( + application_namespace="ssl" ) - self.assertFalse(a == b) - self.assertFalse(b == a) + buff = utils.BytearrayStream() + args = (buff, ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The ApplicationSpecificInformation object is missing the " + "ApplicationData field.", + app_specific_info.write, + *args + ) - def test_equal_on_not_equal_data(self): + def test_repr(self): """ - Test that the equality operator returns False when comparing two - ApplicationSpecificInformation objects with different data. + Test that repr can be applied to an ApplicationSpecificInformation + object. """ - a = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data_1') - ) - b = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data_2') + app_specific_info = attributes.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" ) - self.assertFalse(a == b) - self.assertFalse(b == a) + args = [ + "application_namespace='ssl'", + "application_data='www.example.com'" + ] + self.assertEqual( + "ApplicationSpecificInformation({})".format(", ".join(args)), + repr(app_specific_info) + ) - def test_equal_on_type_mismatch(self): + def test_str(self): """ - Test that the equality operator returns False when comparing a - ApplicationSpecificInformation object to a - non-ApplicationSpecificInformation object. + Test that str can be applied to an ApplicationSpecificInformation + object. """ - a = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data') + app_specific_info = attributes.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" ) - b = "invalid" - self.assertFalse(a == b) - self.assertFalse(b == a) + args = [ + ("application_namespace", "ssl"), + ("application_data", "www.example.com") + ] + value = "{}".format( + ", ".join(['"{}": "{}"'.format(arg[0], arg[1]) for arg in args]) + ) + self.assertEqual( + "{" + value + "}", + str(app_specific_info) + ) - def test_not_equal_on_equal(self): + def test_comparison(self): """ - Test that the inequality operator returns False when comparing - two ApplicationSpecificInformation objects with the same internal + Test that the equality/inequality operators return True/False when + comparing two ApplicationSpecificInformation objects with the same data. """ - a = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data') - ) - b = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data') + a = attributes.ApplicationSpecificInformation() + b = attributes.ApplicationSpecificInformation() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = attributes.ApplicationSpecificInformation( + application_namespace="test_namespace", + application_data="test_data" + ) + b = attributes.ApplicationSpecificInformation( + application_namespace="test_namespace", + application_data="test_data" ) + self.assertTrue(a == b) + self.assertTrue(b == a) self.assertFalse(a != b) self.assertFalse(b != a) - def test_not_equal_on_not_equal_namespace(self): + def test_comparison_on_different_application_namespaces(self): """ - Test that the inequality operator returns True when comparing two - ApplicationSpecificInformation objects with different data. + Test that the equality/inequality operators return False/True when + comparing two ApplicationSpecificInformation objects with different + data. """ - a = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace_1'), - application_data=ApplicationData('test_data') - ) - b = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace_2'), - application_data=ApplicationData('test_data') + a = attributes.ApplicationSpecificInformation( + application_namespace="test_namespace_1" + ) + b = attributes.ApplicationSpecificInformation( + application_namespace="test_namespace_2" ) + self.assertFalse(a == b) + self.assertFalse(b == a) self.assertTrue(a != b) self.assertTrue(b != a) - def test_not_equal_on_not_equal_data(self): + def test_comparison_on_different_application_data(self): """ - Test that the inequality operator returns True when comparing two - ApplicationSpecificInformation objects with different data. + Test that the equality/inequality operators return False/True when + comparing two ApplicationSpecificInformation objects with different + data. """ - a = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data_1') - ) - b = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data_2') + a = attributes.ApplicationSpecificInformation( + application_data="test_data_1" + ) + b = attributes.ApplicationSpecificInformation( + application_data="test_data_2" ) + self.assertFalse(a == b) + self.assertFalse(b == a) self.assertTrue(a != b) self.assertTrue(b != a) - def test_not_equal_on_type_mismatch(self): + def test_comparison_on_type_mismatch(self): """ - Test that the equality operator returns True when comparing a - ApplicationSpecificInformation object to a + Test that the equality/inequality operators return False/True when + comparing an ApplicationSpecificInformation object to a non-ApplicationSpecificInformation object. """ - a = ApplicationSpecificInformation( - application_namespace=ApplicationNamespace('test_namespace'), - application_data=ApplicationData('test_data') + a = attributes.ApplicationSpecificInformation( + application_namespace="test_namespace", + application_data="test_data" ) b = "invalid" - self.assertTrue(a != b) - self.assertTrue(b != a) - - def _test_create(self, application_namespace, application_data): - application_specific_info = ApplicationSpecificInformation.create( - application_namespace, application_data) - - self.assertIsInstance( - application_specific_info, ApplicationSpecificInformation) - - expected = ApplicationNamespace(application_namespace) - observed = application_specific_info.application_namespace - - msg = "expected {0}, observed {1}".format(expected, observed) - self.assertEqual(expected, observed, msg) - - expected = ApplicationData(application_data) - observed = application_specific_info.application_data - - msg = "expected {0}, observed {1}".format(expected, observed) - self.assertEqual(expected, observed, msg) - - def test_create_with_none(self): - """ - Test that an ApplicationSpecificInformation object with no data can be - created using the create class method. - """ - self._test_create(None, None) - - def test_create_with_args(self): - """ - Test that an ApplicationSpecificInformation object with data can be - created using the create class method. - """ - application_namespace = "ssl" - application_data = "www.example.com" - self._test_create(application_namespace, application_data) + self.assertFalse(a == b) + self.assertFalse(b == a) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/attributes/test_attributes.py python-pykmip-0.10.0/kmip/tests/unit/core/attributes/test_attributes.py --- python-pykmip-0.9.1/kmip/tests/unit/core/attributes/test_attributes.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/attributes/test_attributes.py 2020-02-25 16:05:27.000000000 +0000 @@ -15,8 +15,6 @@ from testtools import TestCase -from kmip.core.attributes import ApplicationData -from kmip.core.attributes import ApplicationNamespace from kmip.core.attributes import CertificateType from kmip.core.attributes import CryptographicParameters from kmip.core.attributes import DerivationParameters @@ -378,104 +376,6 @@ self._test_init(b'\x00\x01\x02\x03') -class TestApplicationNamespace(TestCase): - """ - A test suite for the ApplicationNamespace class. - - Since ApplicationNamespace is a simple wrapper for the TextString - primitive, only a few tests pertaining to construction are needed. - """ - - def setUp(self): - super(TestApplicationNamespace, self).setUp() - - def tearDown(self): - super(TestApplicationNamespace, self).tearDown() - - def _test_init(self, value): - if (isinstance(value, str)) or (value is None): - application_namespace = ApplicationNamespace(value) - - if value is None: - value = '' - - msg = "expected {0}, observed {1}".format( - value, application_namespace.value) - self.assertEqual(value, application_namespace.value, msg) - else: - self.assertRaises(TypeError, ApplicationNamespace, value) - - def test_init_with_none(self): - """ - Test that an ApplicationNamespace object can be constructed with no - specified value. - """ - self._test_init(None) - - def test_init_with_valid(self): - """ - Test that an ApplicationNamespace object can be constructed with a - valid, string-type value. - """ - self._test_init("valid") - - def test_init_with_invalid(self): - """ - Test that a TypeError exception is raised when a non-string value is - used to construct an ApplicationNamespace object. - """ - self._test_init(0) - - -class TestApplicationData(TestCase): - """ - A test suite for the ApplicationData class. - - Since ApplicationData is a simple wrapper for the TextString primitive, - only a few tests pertaining to construction are needed. - """ - - def setUp(self): - super(TestApplicationData, self).setUp() - - def tearDown(self): - super(TestApplicationData, self).tearDown() - - def _test_init(self, value): - if (isinstance(value, str)) or (value is None): - application_data = ApplicationData(value) - - if value is None: - value = '' - - msg = "expected {0}, observed {1}".format( - value, application_data.value) - self.assertEqual(value, application_data.value, msg) - else: - self.assertRaises(TypeError, ApplicationData, value) - - def test_init_with_none(self): - """ - Test that an ApplicationData object can be constructed with no - specified value. - """ - self._test_init(None) - - def test_init_with_valid(self): - """ - Test that an ApplicationData object can be constructed with a - valid, string-type value. - """ - self._test_init("valid") - - def test_init_with_invalid(self): - """ - Test that a TypeError exception is raised when a non-string value is - used to construct an ApplicationData object. - """ - self._test_init(0) - - class TestCryptographicParameters(TestCase): """ Test suite for the CryptographicParameters struct. diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/factories/payloads/test_payload.py python-pykmip-0.10.0/kmip/tests/unit/core/factories/payloads/test_payload.py --- python-pykmip-0.9.1/kmip/tests/unit/core/factories/payloads/test_payload.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/factories/payloads/test_payload.py 2020-02-25 16:05:27.000000000 +0000 @@ -112,6 +112,12 @@ enums.Operation.DELETE_ATTRIBUTE ) + def test_create_set_attribute_payload(self): + self._test_not_implemented( + self.factory.create, + enums.Operation.SET_ATTRIBUTE + ) + def test_create_obtain_lease_payload(self): self._test_not_implemented( self.factory.create, diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/factories/payloads/test_request.py python-pykmip-0.10.0/kmip/tests/unit/core/factories/payloads/test_request.py --- python-pykmip-0.9.1/kmip/tests/unit/core/factories/payloads/test_request.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/factories/payloads/test_request.py 2020-02-25 16:05:27.000000000 +0000 @@ -105,16 +105,16 @@ ) def test_create_modify_attribute_payload(self): - self._test_not_implemented( - self.factory.create, - enums.Operation.MODIFY_ATTRIBUTE - ) + payload = self.factory.create(enums.Operation.MODIFY_ATTRIBUTE) + self.assertIsInstance(payload, payloads.ModifyAttributeRequestPayload) def test_create_delete_attribute_payload(self): - self._test_not_implemented( - self.factory.create, - enums.Operation.DELETE_ATTRIBUTE - ) + payload = self.factory.create(enums.Operation.DELETE_ATTRIBUTE) + self.assertIsInstance(payload, payloads.DeleteAttributeRequestPayload) + + def test_create_set_attribute_payload(self): + payload = self.factory.create(enums.Operation.SET_ATTRIBUTE) + self.assertIsInstance(payload, payloads.SetAttributeRequestPayload) def test_create_obtain_lease_payload(self): self._test_not_implemented( diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/factories/payloads/test_response.py python-pykmip-0.10.0/kmip/tests/unit/core/factories/payloads/test_response.py --- python-pykmip-0.9.1/kmip/tests/unit/core/factories/payloads/test_response.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/factories/payloads/test_response.py 2020-02-25 16:05:27.000000000 +0000 @@ -105,16 +105,16 @@ ) def test_create_modify_attribute_payload(self): - self._test_not_implemented( - self.factory.create, - enums.Operation.MODIFY_ATTRIBUTE - ) + payload = self.factory.create(enums.Operation.MODIFY_ATTRIBUTE) + self.assertIsInstance(payload, payloads.ModifyAttributeResponsePayload) def test_create_delete_attribute_payload(self): - self._test_not_implemented( - self.factory.create, - enums.Operation.DELETE_ATTRIBUTE - ) + payload = self.factory.create(enums.Operation.DELETE_ATTRIBUTE) + self.assertIsInstance(payload, payloads.DeleteAttributeResponsePayload) + + def test_create_set_attribute_payload(self): + payload = self.factory.create(enums.Operation.SET_ATTRIBUTE) + self.assertIsInstance(payload, payloads.SetAttributeResponsePayload) def test_create_obtain_lease_payload(self): self._test_not_implemented( diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/factories/test_attribute_values.py python-pykmip-0.10.0/kmip/tests/unit/core/factories/test_attribute_values.py --- python-pykmip-0.9.1/kmip/tests/unit/core/factories/test_attribute_values.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/factories/test_attribute_values.py 2020-02-25 16:05:27.000000000 +0000 @@ -156,10 +156,13 @@ """ Test that a CertificateType attribute can be created. """ - kwargs = {'name': enums.AttributeType.CERTIFICATE_TYPE, - 'value': None} - self.assertRaises( - NotImplementedError, self.factory.create_attribute_value, **kwargs) + certificate_type = self.factory.create_attribute_value( + name=enums.AttributeType.CERTIFICATE_TYPE, + value=enums.CertificateType.X_509 + ) + self.assertIsInstance(certificate_type, primitives.Enumeration) + self.assertEqual(enums.CertificateType.X_509, certificate_type.value) + self.assertEqual(enums.Tags.CERTIFICATE_TYPE, certificate_type.tag) def test_create_certificate_length(self): """ @@ -390,7 +393,21 @@ """ Test that an ObjectGroup attribute can be created. """ - self.skipTest('') + object_group = self.factory.create_attribute_value( + enums.AttributeType.OBJECT_GROUP, + "Group1" + ) + self.assertIsInstance(object_group, primitives.TextString) + self.assertEqual("Group1", object_group.value) + self.assertEqual(enums.Tags.OBJECT_GROUP, object_group.tag) + + object_group = self.factory.create_attribute_value_by_enum( + enums.Tags.OBJECT_GROUP, + "Group1" + ) + self.assertIsInstance(object_group, primitives.TextString) + self.assertEqual("Group1", object_group.value) + self.assertEqual(enums.Tags.OBJECT_GROUP, object_group.tag) def test_create_fresh(self): """ @@ -415,7 +432,55 @@ """ Test that an ApplicationSpecificInformation attribute can be created. """ - self.skipTest('') + attribute = self.factory.create_attribute_value( + enums.AttributeType.APPLICATION_SPECIFIC_INFORMATION, + { + "application_namespace": "ssl", + "application_data": "www.example.com" + } + ) + self.assertIsInstance( + attribute, + attributes.ApplicationSpecificInformation + ) + self.assertEqual("ssl", attribute.application_namespace) + self.assertEqual("www.example.com", attribute.application_data) + + attribute = self.factory.create_attribute_value( + enums.AttributeType.APPLICATION_SPECIFIC_INFORMATION, + None + ) + self.assertIsInstance( + attribute, + attributes.ApplicationSpecificInformation + ) + self.assertIsNone(attribute.application_namespace) + self.assertIsNone(attribute.application_data) + + attribute = self.factory.create_attribute_value_by_enum( + enums.Tags.APPLICATION_SPECIFIC_INFORMATION, + { + "application_namespace": "ssl", + "application_data": "www.example.com" + } + ) + self.assertIsInstance( + attribute, + attributes.ApplicationSpecificInformation + ) + self.assertEqual("ssl", attribute.application_namespace) + self.assertEqual("www.example.com", attribute.application_data) + + attribute = self.factory.create_attribute_value_by_enum( + enums.Tags.APPLICATION_SPECIFIC_INFORMATION, + None + ) + self.assertIsInstance( + attribute, + attributes.ApplicationSpecificInformation + ) + self.assertIsNone(attribute.application_namespace) + self.assertIsNone(attribute.application_data) def test_create_contact_information(self): """ @@ -440,3 +505,23 @@ custom = self.factory.create_attribute_value( enums.AttributeType.CUSTOM_ATTRIBUTE, None) self.assertIsInstance(custom, attributes.CustomAttribute) + + def test_create_sensitive(self): + """ + Test that a Sensitive attribute can be created. + """ + sensitive = self.factory.create_attribute_value( + enums.AttributeType.SENSITIVE, + True + ) + self.assertIsInstance(sensitive, primitives.Boolean) + self.assertTrue(sensitive.value) + self.assertEqual(enums.Tags.SENSITIVE, sensitive.tag) + + sensitive = self.factory.create_attribute_value_by_enum( + enums.Tags.SENSITIVE, + False + ) + self.assertIsInstance(sensitive, primitives.Boolean) + self.assertFalse(sensitive.value) + self.assertEqual(enums.Tags.SENSITIVE, sensitive.tag) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_decrypt.py python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_decrypt.py --- python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_decrypt.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_decrypt.py 2020-02-25 16:05:27.000000000 +0000 @@ -53,9 +53,11 @@ # Initial Counter Value - 1 # Data - 0x0123456789ABCDEF # IV/Counter/Nonce - 0x01 + # Authenticated Encryption Additional Data - 0x011080ff + # Authenticated Encryption Tag - 0x0190FE self.full_encoding = utils.BytearrayStream( - b'\x42\x00\x79\x01\x00\x00\x01\x28' + b'\x42\x00\x79\x01\x00\x00\x01\x48' b'\x42\x00\x94\x07\x00\x00\x00\x24\x62\x34\x66\x61\x65\x65\x31\x30' b'\x2D\x61\x61\x32\x61\x2D\x34\x34\x34\x36\x2D\x38\x61\x64\x34\x2D' b'\x30\x38\x38\x31\x66\x33\x34\x32\x32\x39\x35\x39\x00\x00\x00\x00' @@ -75,6 +77,8 @@ b'\x42\x00\xD1\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' b'\x42\x00\xC2\x08\x00\x00\x00\x08\x01\x23\x45\x67\x89\xAB\xCD\xEF' b'\x42\x00\x3D\x08\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00' + b'\x42\x00\xFE\x08\x00\x00\x00\x04\x01\x10\x80\xFF\x00\x00\x00\x00' + b'\x42\x00\xFF\x08\x00\x00\x00\x03\x01\x90\xFE\x00\x00\x00\x00\x00' ) # Adapted from the full encoding above. This encoding matches the @@ -104,6 +108,8 @@ self.assertEqual(None, payload.cryptographic_parameters) self.assertEqual(None, payload.data) self.assertEqual(None, payload.iv_counter_nonce) + self.assertEqual(None, payload.auth_additional_data) + self.assertEqual(None, payload.auth_tag) def test_init_with_args(self): """ @@ -114,7 +120,9 @@ unique_identifier='00000000-1111-2222-3333-444444444444', cryptographic_parameters=attributes.CryptographicParameters(), data=b'\x01\x02\x03', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF', + auth_tag=b'\x01\x90\xFE' ) self.assertEqual( @@ -127,6 +135,8 @@ ) self.assertEqual(b'\x01\x02\x03', payload.data) self.assertEqual(b'\x01', payload.iv_counter_nonce) + self.assertEqual(b'\x01\x10\x80\xFF', payload.auth_additional_data) + self.assertEqual(b'\x01\x90\xFE', payload.auth_tag) def test_invalid_unique_identifier(self): """ @@ -185,6 +195,34 @@ *args ) + def test_invalid_auth_additional_data(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the authenticated additional data of an Decrypt request payload. + """ + payload = payloads.DecryptRequestPayload() + args = (payload, 'auth_additional_data', 0) + self.assertRaisesRegex( + TypeError, + "authenticated additional data must be bytes", + setattr, + *args + ) + + def test_invalid_auth_tag(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the authenticated encryption tag of an Decrypt request payload. + """ + payload = payloads.DecryptRequestPayload() + args = (payload, 'auth_tag', 0) + self.assertRaisesRegex( + TypeError, + "authenticated encryption tag must be bytes", + setattr, + *args + ) + def test_read(self): """ Test that a Decrypt request payload can be read from a data stream. @@ -195,8 +233,10 @@ self.assertEqual(None, payload.cryptographic_parameters) self.assertEqual(None, payload.data) self.assertEqual(None, payload.iv_counter_nonce) + self.assertEqual(None, payload.auth_additional_data) + self.assertEqual(None, payload.auth_tag) - payload.read(self.full_encoding) + payload.read(self.full_encoding, enums.KMIPVersion.KMIP_1_4) self.assertEqual( 'b4faee10-aa2a-4446-8ad4-0881f3422959', @@ -245,6 +285,8 @@ ) self.assertEqual(b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', payload.data) self.assertEqual(b'\x01', payload.iv_counter_nonce) + self.assertEqual(b'\x01\x10\x80\xFF', payload.auth_additional_data) + self.assertEqual(b'\x01\x90\xFE', payload.auth_tag) def test_read_partial(self): """ @@ -302,10 +344,12 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF', + auth_tag=b'\x01\x90\xFE' ) stream = utils.BytearrayStream() - payload.write(stream) + payload.write(stream, enums.KMIPVersion.KMIP_1_4) self.assertEqual(len(self.full_encoding), len(stream)) self.assertEqual(str(self.full_encoding), str(stream)) @@ -369,7 +413,9 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF', + auth_tag=b'\x01\x90\xFE' ) b = payloads.DecryptRequestPayload( unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', @@ -390,7 +436,9 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF', + auth_tag=b'\x01\x90\xFE' ) self.assertTrue(a == b) @@ -452,6 +500,30 @@ self.assertFalse(a == b) self.assertFalse(b == a) + def test_equal_on_not_equal_auth_additional_data(self): + """ + Test that the equality operator returns False when comparing two + Decrypt request payloads with different authenticated additional data + values. + """ + a = payloads.DecryptRequestPayload(auth_additional_data=b'\x22') + b = payloads.DecryptRequestPayload(auth_additional_data=b'\xAA') + + self.assertFalse(a == b) + self.assertFalse(b == a) + + def test_equal_on_not_equal_auth_tag(self): + """ + Test that the equality operator returns False when comparing two + Decrypt request payloads with different authenticated encryption tag + values. + """ + a = payloads.DecryptRequestPayload(auth_tag=b'\x22') + b = payloads.DecryptRequestPayload(auth_tag=b'\xAA') + + self.assertFalse(a == b) + self.assertFalse(b == a) + def test_equal_on_type_mismatch(self): """ Test that the equality operator returns False when comparing two @@ -493,7 +565,9 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF', + auth_tag=b'\x01\x90\xFE' ) b = payloads.DecryptRequestPayload( unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', @@ -514,7 +588,9 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF', + auth_tag=b'\x01\x90\xFE' ) self.assertFalse(a != b) @@ -576,6 +652,30 @@ self.assertTrue(a != b) self.assertTrue(b != a) + def test_not_equal_on_not_equal_auth_additional_data(self): + """ + Test that the inequality operator returns True when comparing two + Decrypt request payloads with different authenticated additional data + values. + """ + a = payloads.DecryptRequestPayload(auth_additional_data=b'\x22') + b = payloads.DecryptRequestPayload(auth_additional_data=b'\xAA') + + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_not_equal_on_not_equal_auth_tag(self): + """ + Test that the inequality operator returns True when comparing two + Decrypt request payloads with different authenticated encryption tag + values. + """ + a = payloads.DecryptRequestPayload(auth_tag=b'\x22') + b = payloads.DecryptRequestPayload(auth_tag=b'\xAA') + + self.assertTrue(a != b) + self.assertTrue(b != a) + def test_not_equal_on_type_mismatch(self): """ Test that the inequality operator returns True when comparing two @@ -610,7 +710,9 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF', + auth_tag=b'\x01\x90\xFE' ) expected = ( "DecryptRequestPayload(" @@ -631,7 +733,9 @@ "counter_length=0, " "initial_counter_value=1), " "data=" + str(b'\x01\x23\x45\x67\x89\xAB\xCD\xEF') + ", " - "iv_counter_nonce=" + str(b'\x01') + ")" + "iv_counter_nonce=" + str(b'\x01') + ", " + "auth_additional_data=" + str(b'\x01\x10\x80\xFF') + ", " + "auth_tag=" + str(b'\x01\x90\xFE') + ")" ) observed = repr(payload) @@ -661,14 +765,18 @@ unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', cryptographic_parameters=cryptographic_parameters, data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF', + auth_tag=b'\x01\x90\xFE' ) expected = str({ 'unique_identifier': 'b4faee10-aa2a-4446-8ad4-0881f3422959', 'cryptographic_parameters': cryptographic_parameters, 'data': b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - 'iv_counter_nonce': b'\x01' + 'iv_counter_nonce': b'\x01', + 'auth_additional_data': b'\x01\x10\x80\xFF', + 'auth_tag': b'\x01\x90\xFE' }) observed = str(payload) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_delete_attribute.py python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_delete_attribute.py --- python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_delete_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_delete_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,1166 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools + +from kmip.core import enums +from kmip.core import exceptions +from kmip.core import objects +from kmip.core import primitives +from kmip.core import utils + +from kmip.core.messages import payloads + + +class TestDeleteAttributeRequestPayload(testtools.TestCase): + """ + A unit test suite for the DeleteAttribute request payload. + """ + + def setUp(self): + super(TestDeleteAttributeRequestPayload, self).setUp() + + # This encoding was adapted from test case 3.1.4-7 from the KMIP 1.1 + # test suite. The Attribute Index was manually added. + # + # This encoding matches the following set of values: + # Request Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # Attribute Name - x-attribute1 + # Attribute Index - 1 + self.full_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x58' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x00\x0A\x07\x00\x00\x00\x0C' + b'\x78\x2D\x61\x74\x74\x72\x69\x62\x75\x74\x65\x31\x00\x00\x00\x00' + b'\x42\x00\x09\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + ) + + # This encoding was taken from test case 3.1.4-7 from the KMIP 1.1 + # test suite. + # + # This encoding matches the following set of values: + # Request Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # Attribute Name - x-attribute1 + self.no_attribute_index_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x48' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x00\x0A\x07\x00\x00\x00\x0C' + b'\x78\x2D\x61\x74\x74\x72\x69\x62\x75\x74\x65\x31\x00\x00\x00\x00' + ) + + # This encoding was adapted from test case 3.1.4-7 from the KMIP 1.1 + # test suite. The current attribute and the attribute reference were + # manually added. + # + # This encoding matches the following set of values. + # Request Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # Current Attribute + # Cryptographic Algorithm - AES + # Attribute Reference + # Vendor Identification - Acme Corporation + # Attribute Name - Delivery Date + self.full_encoding_kmip_2_0 = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x80' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x01\x3C\x01\x00\x00\x00\x10' + b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00' + b'\x42\x01\x3B\x01\x00\x00\x00\x30' + b'\x42\x00\x9D\x07\x00\x00\x00\x10' + b'\x41\x63\x6D\x65\x20\x43\x6F\x72\x70\x6F\x72\x61\x74\x69\x6F\x6E' + b'\x42\x00\x0A\x07\x00\x00\x00\x0D' + b'\x44\x65\x6C\x69\x76\x65\x72\x79\x20\x44\x61\x74\x65\x00\x00\x00' + ) + + # This encoding was adapted from test case 3.1.4-7 from the KMIP 1.1 + # test suite. The current attribute and the attribute reference were + # manually added. + # + # This encoding matches the following set of values. + # Request Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # Attribute Reference + # Vendor Identification - Acme Corporation + # Attribute Name - Delivery Date + self.no_current_attribute_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x68' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x01\x3B\x01\x00\x00\x00\x30' + b'\x42\x00\x9D\x07\x00\x00\x00\x10' + b'\x41\x63\x6D\x65\x20\x43\x6F\x72\x70\x6F\x72\x61\x74\x69\x6F\x6E' + b'\x42\x00\x0A\x07\x00\x00\x00\x0D' + b'\x44\x65\x6C\x69\x76\x65\x72\x79\x20\x44\x61\x74\x65\x00\x00\x00' + ) + + # This encoding was adapted from test case 3.1.4-7 from the KMIP 1.1 + # test suite. The current attribute and the attribute reference were + # manually added. + # + # This encoding matches the following set of values. + # Request Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # Current Attribute + # Cryptographic Algorithm - AES + self.no_attribute_reference_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x48' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x01\x3C\x01\x00\x00\x00\x10' + b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00' + ) + + self.empty_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x00' + ) + + def tearDown(self): + super(TestDeleteAttributeRequestPayload, self).tearDown() + + def test_invalid_unique_identifier(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the unique identifier of a DeleteAttribute request payload. + """ + kwargs = {"unique_identifier": 0} + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + payloads.DeleteAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.DeleteAttributeRequestPayload(), + "unique_identifier", + 0 + ) + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + setattr, + *args + ) + + def test_invalid_attribute_name(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the attribute name of a DeleteAttribute request payload. + """ + kwargs = {"attribute_name": 0} + self.assertRaisesRegex( + TypeError, + "The attribute name must be a string.", + payloads.DeleteAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.DeleteAttributeRequestPayload(), + "attribute_name", + 0 + ) + self.assertRaisesRegex( + TypeError, + "The attribute name must be a string.", + setattr, + *args + ) + + def test_invalid_attribute_index(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the attribute index of a DeleteAttribute request payload. + """ + kwargs = {"attribute_index": "invalid"} + self.assertRaisesRegex( + TypeError, + "The attribute index must be an integer.", + payloads.DeleteAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.DeleteAttributeRequestPayload(), + "attribute_index", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The attribute index must be an integer.", + setattr, + *args + ) + + def test_invalid_current_attribute(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the current attribute of a DeleteAttribute request payload. + """ + kwargs = {"current_attribute": "invalid"} + self.assertRaisesRegex( + TypeError, + "The current attribute must be a CurrentAttribute object.", + payloads.DeleteAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.DeleteAttributeRequestPayload(), + "current_attribute", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The current attribute must be a CurrentAttribute object.", + setattr, + *args + ) + + def test_invalid_attribute_reference(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the attribute reference of a DeleteAttribute request payload. + """ + kwargs = {"attribute_reference": "invalid"} + self.assertRaisesRegex( + TypeError, + "The attribute reference must be an AttributeReference object.", + payloads.DeleteAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.DeleteAttributeRequestPayload(), + "attribute_reference", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The attribute reference must be an AttributeReference object.", + setattr, + *args + ) + + def test_read(self): + """ + Test that a DeleteAttribute request payload can be read from a buffer. + """ + payload = payloads.DeleteAttributeRequestPayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute_name) + self.assertIsNone(payload.attribute_index) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.attribute_reference) + + payload.read(self.full_encoding) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertEqual("x-attribute1", payload.attribute_name) + self.assertEqual(1, payload.attribute_index) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.attribute_reference) + + def test_read_kmip_2_0(self): + """ + Test that a DeleteAttribute request payload can be read from a buffer + with KMIP 2.0 features. + """ + payload = payloads.DeleteAttributeRequestPayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute_name) + self.assertIsNone(payload.attribute_index) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.attribute_reference) + + payload.read( + self.full_encoding_kmip_2_0, + kmip_version=enums.KMIPVersion.KMIP_2_0 + ) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertIsNone(payload.attribute_name) + self.assertIsNone(payload.attribute_index) + self.assertEqual( + objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + payload.current_attribute + ) + self.assertEqual( + objects.AttributeReference( + vendor_identification="Acme Corporation", + attribute_name="Delivery Date" + ), + payload.attribute_reference + ) + + def test_read_no_attribute_name(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded attribute name is used to decode + a DeleteAttribute request payload. + """ + payload = payloads.DeleteAttributeRequestPayload() + args = (self.empty_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The DeleteAttribute request payload encoding is missing the " + "attribute name field.", + payload.read, + *args + ) + + def test_read_kmip_2_0_no_current_attribute_or_attribute_reference(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded current attribute or attribute + reference is used to decode a DeleteAttribute request payload. + """ + payload = payloads.DeleteAttributeRequestPayload() + args = (self.empty_encoding, ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_2_0} + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The DeleteAttribute encoding is missing either the current " + "attribute or the attribute reference field.", + payload.read, + *args, + **kwargs + ) + + def test_read_no_attribute_index(self): + """ + Test that a DeleteAttribute request payload can be read from a buffer + without including the attribute index encoding. + """ + payload = payloads.DeleteAttributeRequestPayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute_name) + self.assertIsNone(payload.attribute_index) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.attribute_reference) + + payload.read(self.no_attribute_index_encoding) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertEqual("x-attribute1", payload.attribute_name) + self.assertIsNone(payload.attribute_index) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.attribute_reference) + + def test_read_no_current_attribute(self): + """ + Test that a DeleteAttribute request payload can be read from a buffer + without including the current attribute encoding. + """ + payload = payloads.DeleteAttributeRequestPayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute_name) + self.assertIsNone(payload.attribute_index) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.attribute_reference) + + payload.read( + self.no_current_attribute_encoding, + kmip_version=enums.KMIPVersion.KMIP_2_0 + ) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertIsNone(payload.attribute_name) + self.assertIsNone(payload.attribute_index) + self.assertIsNone(payload.current_attribute) + self.assertEqual( + objects.AttributeReference( + vendor_identification="Acme Corporation", + attribute_name="Delivery Date" + ), + payload.attribute_reference + ) + + def test_read_no_attribute_reference(self): + """ + Test that a DeleteAttribute request payload can be read from a buffer + without including the attribute reference encoding. + """ + payload = payloads.DeleteAttributeRequestPayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute_name) + self.assertIsNone(payload.attribute_index) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.attribute_reference) + + payload.read( + self.no_attribute_reference_encoding, + kmip_version=enums.KMIPVersion.KMIP_2_0 + ) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertIsNone(payload.attribute_name) + self.assertIsNone(payload.attribute_index) + self.assertEqual( + objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + payload.current_attribute + ) + self.assertIsNone(payload.attribute_reference) + + def test_write(self): + """ + Test that a DeleteAttribute request payload can be written to a buffer. + """ + payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute_name="x-attribute1", + attribute_index=1 + ) + + buffer = utils.BytearrayStream() + payload.write(buffer) + + self.assertEqual(len(self.full_encoding), len(buffer)) + self.assertEqual(str(self.full_encoding), str(buffer)) + + def test_write_kmip_2_0(self): + """ + Test that a DeleteAttribute request payload can be written to a buffer + with KMIP 2.0 features. + """ + payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + current_attribute=objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + attribute_reference=objects.AttributeReference( + vendor_identification="Acme Corporation", + attribute_name="Delivery Date" + ) + ) + + buffer = utils.BytearrayStream() + payload.write(buffer, kmip_version=enums.KMIPVersion.KMIP_2_0) + + self.assertEqual(len(self.full_encoding_kmip_2_0), len(buffer)) + self.assertEqual(str(self.full_encoding_kmip_2_0), str(buffer)) + + def test_write_no_attribute_name(self): + """ + Test that an InvalidField error is raised when attempting to write + a DeleteAttribute request payload to a buffer with no attribute name + field specified. + """ + payload = payloads.DeleteAttributeRequestPayload() + + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The DeleteAttribute request payload is missing the attribute " + "name field.", + payload.write, + *args + ) + + def test_write_no_current_attribute_or_attribute_reference(self): + """ + Test that an InvalidField error is raised when attempting to write + a DeleteAttribute request payload to a buffer with KMIP 2.0 features + with no current attribute or attribute reference field specified. + """ + payload = payloads.DeleteAttributeRequestPayload() + + args = (utils.BytearrayStream(), ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_2_0} + self.assertRaisesRegex( + exceptions.InvalidField, + "The DeleteAttribute request payload is missing either the " + "current attribute or the attribute reference field.", + payload.write, + *args, + **kwargs + ) + + def test_repr(self): + """ + Test that repr can be applied to a DeleteAttribute request payload. + """ + payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute_name="x-attribute1", + attribute_index=1 + ) + + args = [ + "unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959'", + "attribute_name='x-attribute1'", + "attribute_index=1", + "current_attribute=None", + "attribute_reference=None" + ] + self.assertEqual( + "DeleteAttributeRequestPayload({})".format(", ".join(args)), + repr(payload) + ) + + def test_str(self): + """ + Test that str can be applied to a DeleteAttribute request payload. + """ + payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute_name="x-attribute1", + attribute_index=1 + ) + s = str( + { + "unique_identifier": "b4faee10-aa2a-4446-8ad4-0881f3422959", + "attribute_name": "x-attribute1", + "attribute_index": 1, + "current_attribute": None, + "attribute_reference": None + } + ) + self.assertEqual(s, str(payload)) + + def test_comparison(self): + """ + Test that the equality/inequality operators return True/False when + comparing two DeleteAttribute request payloads with the same data. + """ + a = payloads.DeleteAttributeRequestPayload() + b = payloads.DeleteAttributeRequestPayload() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = payloads.DeleteAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute_name="x-attribute1", + attribute_index=1, + current_attribute=objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + attribute_reference=objects.AttributeReference( + vendor_identification="Acme Corporation", + attribute_name="Delivery Date" + ) + ) + b = payloads.DeleteAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute_name="x-attribute1", + attribute_index=1, + current_attribute=objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + attribute_reference=objects.AttributeReference( + vendor_identification="Acme Corporation", + attribute_name="Delivery Date" + ) + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_unique_identifiers(self): + """ + Test that the equality/inequality operators return False/True when + comparing two DeleteAttribute request payloads with different unique + identifiers. + """ + a = payloads.DeleteAttributeRequestPayload(unique_identifier="1") + b = payloads.DeleteAttributeRequestPayload(unique_identifier="2") + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_attribute_names(self): + """ + Test that the equality/inequality operators return False/True when + comparing two DeleteAttribute request payloads with different + attribute names. + """ + a = payloads.DeleteAttributeRequestPayload(attribute_name="1") + b = payloads.DeleteAttributeRequestPayload(attribute_name="2") + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_attribute_indices(self): + """ + Test that the equality/inequality operators return False/True when + comparing two DeleteAttribute request payloads with different + attribute indices. + """ + a = payloads.DeleteAttributeRequestPayload(attribute_index=1) + b = payloads.DeleteAttributeRequestPayload(attribute_index=2) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_current_attributes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two DeleteAttribute request payloads with different current + attributes. + """ + a = payloads.DeleteAttributeRequestPayload( + current_attribute=objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + b = payloads.DeleteAttributeRequestPayload( + current_attribute=objects.CurrentAttribute( + attribute=primitives.Integer( + 128, + enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_attribute_references(self): + """ + Test that the equality/inequality operators return False/True when + comparing two DeleteAttribute request payloads with different + attribute references. + """ + a = payloads.DeleteAttributeRequestPayload( + attribute_reference=objects.AttributeReference( + vendor_identification="Acme Corporation", + attribute_name="Delivery Date" + ) + ) + b = payloads.DeleteAttributeRequestPayload( + attribute_reference=objects.AttributeReference( + vendor_identification="Acme Corporation", + attribute_name="Delivery Estimate" + ) + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparining a DeleteAttribute request payload against a different type. + """ + a = payloads.DeleteAttributeRequestPayload() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + +class TestDeleteAttributeResponsePayload(testtools.TestCase): + """ + A unit test suite for the DeleteAttribute response payload. + """ + + def setUp(self): + super(TestDeleteAttributeResponsePayload, self).setUp() + + # This encoding was taken from test case 3.1.4-7 from the KMIP 1.1 + # test suite. + # + # This encoding matches the following set of values: + # Response Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # Attribute + # Attribute Name - x-attribute1 + # Attribute Value - ModifiedValue1 + self.full_encoding = utils.BytearrayStream( + b'\x42\x00\x7C\x01\x00\x00\x00\x68' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x00\x08\x01\x00\x00\x00\x30' + b'\x42\x00\x0A\x07\x00\x00\x00\x0C' + b'\x78\x2D\x61\x74\x74\x72\x69\x62\x75\x74\x65\x31\x00\x00\x00\x00' + b'\x42\x00\x0B\x07\x00\x00\x00\x0E' + b'\x4D\x6F\x64\x69\x66\x69\x65\x64\x56\x61\x6C\x75\x65\x31\x00\x00' + ) + + # This encoding was adapted from test case 3.1.4-7 from the KMIP 1.1 + # test suite. The attribute field was removed. + # + # This encoding matches the following set of values: + # Response Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + self.no_attribute_encoding = utils.BytearrayStream( + b'\x42\x00\x7C\x01\x00\x00\x00\x30' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + ) + + # This encoding was adapt from test case 3.1.4-7 from the KMIP 1.1 + # test suite. The attribute field was removed. + # + # This encoding matches the following set of values: + # Response Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + self.full_encoding_kmip_2_0 = utils.BytearrayStream( + b'\x42\x00\x7C\x01\x00\x00\x00\x30' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + + ) + + self.empty_encoding = utils.BytearrayStream( + b'\x42\x00\x7C\x01\x00\x00\x00\x00' + ) + + def tearDown(self): + super(TestDeleteAttributeResponsePayload, self).tearDown() + + def test_invalid_unique_identifier(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the unique identifier of a DeleteAttribute response payload. + """ + kwargs = {"unique_identifier": 0} + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + payloads.DeleteAttributeResponsePayload, + **kwargs + ) + + args = ( + payloads.DeleteAttributeResponsePayload(), + "unique_identifier", + 0 + ) + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + setattr, + *args + ) + + def test_invalid_attribute(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the attribute of a DeleteAttribute response payload. + """ + kwargs = {"attribute": 0} + self.assertRaisesRegex( + TypeError, + "The attribute must be an Attribute object.", + payloads.DeleteAttributeResponsePayload, + **kwargs + ) + + args = ( + payloads.DeleteAttributeResponsePayload(), + "attribute", + 0 + ) + self.assertRaisesRegex( + TypeError, + "The attribute must be an Attribute object.", + setattr, + *args + ) + + def test_read(self): + """ + Test that a DeleteAttribute response payload can be read from a buffer. + """ + payload = payloads.DeleteAttributeResponsePayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute) + + payload.read(self.full_encoding) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertEqual( + objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + "ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ), + payload.attribute + ) + + def test_read_kmip_2_0(self): + """ + Test that a DeleteAttribute response payload can be read from a buffer + with KMIP 2.0 features. + """ + payload = payloads.DeleteAttributeResponsePayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute) + + payload.read( + self.full_encoding_kmip_2_0, + kmip_version=enums.KMIPVersion.KMIP_2_0 + ) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertIsNone(payload.attribute) + + def test_read_no_unique_identifier(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded unique identifier is used to decode + a DeleteAttribute response payload. + """ + payload = payloads.DeleteAttributeResponsePayload() + args = (self.empty_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The DeleteAttribute response payload encoding is missing the " + "unique identifier field.", + payload.read, + *args + ) + + def test_read_no_attribute(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded attribute is used to decode a + DeleteAttribute response payload. + """ + payload = payloads.DeleteAttributeResponsePayload() + args = (self.no_attribute_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The DeleteAttribute response payload encoding is missing the " + "attribute field.", + payload.read, + *args + ) + + def test_write(self): + """ + Test that a DeleteAttribute response payload can be written to a + buffer. + """ + payload = payloads.DeleteAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + "ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + + buffer = utils.BytearrayStream() + payload.write(buffer) + + self.assertEqual(len(self.full_encoding), len(buffer)) + self.assertEqual(str(self.full_encoding), str(buffer)) + + def test_write_kmip_2_0(self): + """ + Test that a DeleteAttribute response payload can be written to a buffer + with KMIP 2.0 features. + """ + payload = payloads.DeleteAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + + buffer = utils.BytearrayStream() + payload.write(buffer, kmip_version=enums.KMIPVersion.KMIP_2_0) + + self.assertEqual(len(self.full_encoding_kmip_2_0), len(buffer)) + self.assertEqual(str(self.full_encoding_kmip_2_0), str(buffer)) + + def test_write_no_unique_identifier(self): + """ + Test that an InvalidField error is raised when attempting to write + a DeleteAttribute response payload to a buffer with no unique + identifier field specified. + """ + payload = payloads.DeleteAttributeResponsePayload() + + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The DeleteAttribute response payload is missing the unique " + "identifier field.", + payload.write, + *args + ) + + def test_write_no_attribute(self): + """ + Test that an InvalidField error is raised when attempting to write + a DeleteAttribute response payload to a buffer with no attribute field + specified. + """ + payload = payloads.DeleteAttributeResponsePayload( + unique_identifier="1" + ) + + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The DeleteAttribute response payload is missing the attribute " + "field.", + payload.write, + *args + ) + + def test_repr(self): + """ + Test that repr can be applied to a DeleteAttribute response payload. + """ + payload = payloads.DeleteAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + "ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + + args = [ + "unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959'", + "attribute=Attribute(" + "attribute_name=AttributeName(value='x-attribute1'), " + "attribute_index=None, " + "attribute_value=TextString(value='ModifiedValue1'))" + ] + self.assertEqual( + "DeleteAttributeResponsePayload({})".format(", ".join(args)), + repr(payload) + ) + + def test_str(self): + """ + Test that str can be applied to a DeleteAttribute response payload. + """ + payload = payloads.DeleteAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + "ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + s = str( + { + "unique_identifier": "b4faee10-aa2a-4446-8ad4-0881f3422959", + "attribute": str( + objects.Attribute( + attribute_name=objects.Attribute.AttributeName( + "x-attribute1" + ), + attribute_value=primitives.TextString( + "ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + } + ) + self.assertEqual(s, str(payload)) + + def test_comparison(self): + """ + Test that the equality/inequality operators return True/False when + comparing two DeleteAttribute response payloads with the same data. + """ + a = payloads.DeleteAttributeResponsePayload() + b = payloads.DeleteAttributeResponsePayload() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = payloads.DeleteAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + "ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + b = payloads.DeleteAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + "ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_unique_identifiers(self): + """ + Test that the equality/inequality operators return False/True when + comparing two DeleteAttribute response payloads with different unique + identifiers. + """ + a = payloads.DeleteAttributeResponsePayload(unique_identifier="1") + b = payloads.DeleteAttributeResponsePayload(unique_identifier="2") + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_attributes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two DeleteAttribute response payloads with different + attributes. + """ + a = payloads.DeleteAttributeResponsePayload( + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + "ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + b = payloads.DeleteAttributeResponsePayload( + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute2"), + attribute_value=primitives.TextString( + "ModifiedValue2", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparining a DeleteAttribute response payload against a different + type. + """ + a = payloads.DeleteAttributeResponsePayload() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_encrypt.py python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_encrypt.py --- python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_encrypt.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_encrypt.py 2020-02-25 16:05:27.000000000 +0000 @@ -53,9 +53,10 @@ # Initial Counter Value - 1 # Data - 0x0123456789ABCDEF # IV/Counter/Nonce - 0x01 + # Authenticated Encryption Additional Data - 0x011080ff self.full_encoding = utils.BytearrayStream( - b'\x42\x00\x79\x01\x00\x00\x01\x28' + b'\x42\x00\x79\x01\x00\x00\x01\x38' b'\x42\x00\x94\x07\x00\x00\x00\x24\x62\x34\x66\x61\x65\x65\x31\x30' b'\x2D\x61\x61\x32\x61\x2D\x34\x34\x34\x36\x2D\x38\x61\x64\x34\x2D' b'\x30\x38\x38\x31\x66\x33\x34\x32\x32\x39\x35\x39\x00\x00\x00\x00' @@ -75,6 +76,7 @@ b'\x42\x00\xD1\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' b'\x42\x00\xC2\x08\x00\x00\x00\x08\x01\x23\x45\x67\x89\xAB\xCD\xEF' b'\x42\x00\x3D\x08\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00' + b'\x42\x00\xFE\x08\x00\x00\x00\x04\x01\x10\x80\xFF\x00\x00\x00\x00' ) # Adapted from the full encoding above. This encoding matches the @@ -104,6 +106,7 @@ self.assertEqual(None, payload.cryptographic_parameters) self.assertEqual(None, payload.data) self.assertEqual(None, payload.iv_counter_nonce) + self.assertEqual(None, payload.auth_additional_data) def test_init_with_args(self): """ @@ -114,7 +117,8 @@ unique_identifier='00000000-1111-2222-3333-444444444444', cryptographic_parameters=attributes.CryptographicParameters(), data=b'\x01\x02\x03', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF' ) self.assertEqual( @@ -127,6 +131,7 @@ ) self.assertEqual(b'\x01\x02\x03', payload.data) self.assertEqual(b'\x01', payload.iv_counter_nonce) + self.assertEqual(b'\x01\x10\x80\xFF', payload.auth_additional_data) def test_invalid_unique_identifier(self): """ @@ -185,6 +190,20 @@ *args ) + def test_invalid_auth_additional_data(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the authenticated additional data of an Encrypt request payload. + """ + payload = payloads.EncryptRequestPayload() + args = (payload, 'auth_additional_data', 0) + self.assertRaisesRegex( + TypeError, + "authenticated additional data must be bytes", + setattr, + *args + ) + def test_read(self): """ Test that an Encrypt request payload can be read from a data stream. @@ -195,8 +214,9 @@ self.assertEqual(None, payload.cryptographic_parameters) self.assertEqual(None, payload.data) self.assertEqual(None, payload.iv_counter_nonce) + self.assertEqual(None, payload.auth_additional_data) - payload.read(self.full_encoding) + payload.read(self.full_encoding, enums.KMIPVersion.KMIP_1_4) self.assertEqual( 'b4faee10-aa2a-4446-8ad4-0881f3422959', @@ -245,6 +265,7 @@ ) self.assertEqual(b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', payload.data) self.assertEqual(b'\x01', payload.iv_counter_nonce) + self.assertEqual(b'\x01\x10\x80\xFF', payload.auth_additional_data) def test_read_partial(self): """ @@ -302,10 +323,11 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF' ) stream = utils.BytearrayStream() - payload.write(stream) + payload.write(stream, enums.KMIPVersion.KMIP_1_4) self.assertEqual(len(self.full_encoding), len(stream)) self.assertEqual(str(self.full_encoding), str(stream)) @@ -369,7 +391,8 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF' ) b = payloads.EncryptRequestPayload( unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', @@ -390,7 +413,8 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF' ) self.assertTrue(a == b) @@ -452,6 +476,18 @@ self.assertFalse(a == b) self.assertFalse(b == a) + def test_equal_on_not_equal_auth_additional_data(self): + """ + Test that the equality operator returns False when comparing two + Encrypt request payloads with different authenticated additional data + values. + """ + a = payloads.EncryptRequestPayload(auth_additional_data=b'\x22') + b = payloads.EncryptRequestPayload(auth_additional_data=b'\xAA') + + self.assertFalse(a == b) + self.assertFalse(b == a) + def test_equal_on_type_mismatch(self): """ Test that the equality operator returns False when comparing two @@ -576,6 +612,18 @@ self.assertTrue(a != b) self.assertTrue(b != a) + def test_not_equal_on_not_equal_auth_additional_data(self): + """ + Test that the inequality operator returns True when comparing two + Encrypt request payloads with different authenticated additional data + values. + """ + a = payloads.EncryptRequestPayload(auth_additional_data=b'\x22') + b = payloads.EncryptRequestPayload(auth_additional_data=b'\xAA') + + self.assertTrue(a != b) + self.assertTrue(b != a) + def test_not_equal_on_type_mismatch(self): """ Test that the inequality operator returns True when comparing two @@ -610,7 +658,8 @@ initial_counter_value=1 ), data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF' ) expected = ( "EncryptRequestPayload(" @@ -631,7 +680,8 @@ "counter_length=0, " "initial_counter_value=1), " "data=" + str(b'\x01\x23\x45\x67\x89\xAB\xCD\xEF') + ", " - "iv_counter_nonce=" + str(b'\x01') + ")" + "iv_counter_nonce=" + str(b'\x01') + ", " + "auth_additional_data=" + str(b'\x01\x10\x80\xFF') + ")" ) observed = repr(payload) @@ -661,14 +711,16 @@ unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', cryptographic_parameters=cryptographic_parameters, data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_additional_data=b'\x01\x10\x80\xFF' ) expected = str({ 'unique_identifier': 'b4faee10-aa2a-4446-8ad4-0881f3422959', 'cryptographic_parameters': cryptographic_parameters, 'data': b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - 'iv_counter_nonce': b'\x01' + 'iv_counter_nonce': b'\x01', + 'auth_additional_data': b'\x01\x10\x80\xFF' }) observed = str(payload) @@ -692,14 +744,16 @@ # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 # Data - 0x0123456789ABCDEF # IV/Counter/Nonce - 0x01 + # Authenticated Encryption Tag - 0x0190FE self.full_encoding = utils.BytearrayStream( - b'\x42\x00\x7C\x01\x00\x00\x00\x50' + b'\x42\x00\x7C\x01\x00\x00\x00\x60' b'\x42\x00\x94\x07\x00\x00\x00\x24\x62\x34\x66\x61\x65\x65\x31\x30' b'\x2D\x61\x61\x32\x61\x2D\x34\x34\x34\x36\x2D\x38\x61\x64\x34\x2D' b'\x30\x38\x38\x31\x66\x33\x34\x32\x32\x39\x35\x39\x00\x00\x00\x00' b'\x42\x00\xC2\x08\x00\x00\x00\x08\x01\x23\x45\x67\x89\xAB\xCD\xEF' b'\x42\x00\x3D\x08\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00' + b'\x42\x00\xFF\x08\x00\x00\x00\x03\x01\x90\xFE\x00\x00\x00\x00\x00' ) # Adapted from the full encoding above. This encoding matches the @@ -743,6 +797,7 @@ self.assertEqual(None, payload.unique_identifier) self.assertEqual(None, payload.data) self.assertEqual(None, payload.iv_counter_nonce) + self.assertEqual(None, payload.auth_tag) def test_init_with_args(self): """ @@ -752,7 +807,8 @@ payload = payloads.EncryptResponsePayload( unique_identifier='00000000-1111-2222-3333-444444444444', data=b'\x01\x02\x03', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_tag=b'\x01\x90\xFE' ) self.assertEqual( @@ -761,6 +817,7 @@ ) self.assertEqual(b'\x01\x02\x03', payload.data) self.assertEqual(b'\x01', payload.iv_counter_nonce) + self.assertEqual(b'\x01\x90\xFE', payload.auth_tag) def test_invalid_unique_identifier(self): """ @@ -804,6 +861,20 @@ *args ) + def test_invalid_auth_tag(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the authenticated encryption tag of an Encrypt response payload. + """ + payload = payloads.EncryptResponsePayload() + args = (payload, 'auth_tag', 0) + self.assertRaisesRegex( + TypeError, + "authenticated encryption tag must be bytes", + setattr, + *args + ) + def test_read(self): """ Test that an Encrypt response payload can be read from a data stream. @@ -813,8 +884,9 @@ self.assertEqual(None, payload.unique_identifier) self.assertEqual(None, payload.data) self.assertEqual(None, payload.iv_counter_nonce) + self.assertEqual(None, payload.auth_tag) - payload.read(self.full_encoding) + payload.read(self.full_encoding, enums.KMIPVersion.KMIP_1_4) self.assertEqual( 'b4faee10-aa2a-4446-8ad4-0881f3422959', @@ -822,6 +894,7 @@ ) self.assertEqual(b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', payload.data) self.assertEqual(b'\x01', payload.iv_counter_nonce) + self.assertEqual(b'\x01\x90\xFE', payload.auth_tag) def test_read_partial(self): """ @@ -873,10 +946,11 @@ payload = payloads.EncryptResponsePayload( unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_tag=b'\x01\x90\xFE' ) stream = utils.BytearrayStream() - payload.write(stream) + payload.write(stream, enums.KMIPVersion.KMIP_1_4) self.assertEqual(len(self.full_encoding), len(stream)) self.assertEqual(str(self.full_encoding), str(stream)) @@ -938,12 +1012,14 @@ a = payloads.EncryptResponsePayload( unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_tag=b'\x01\x90\xFE' ) b = payloads.EncryptResponsePayload( unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_tag=b'\x01\x90\xFE' ) self.assertTrue(a == b) @@ -986,6 +1062,18 @@ self.assertFalse(a == b) self.assertFalse(b == a) + def test_equal_on_not_equal_auth_tag(self): + """ + Test that the equality operator returns False when comparing two + Encrypt response payloads with different authenticated encryption tag + values. + """ + a = payloads.EncryptResponsePayload(auth_tag=b'\x22') + b = payloads.EncryptResponsePayload(auth_tag=b'\xAA') + + self.assertFalse(a == b) + self.assertFalse(b == a) + def test_equal_on_type_mismatch(self): """ Test that the equality operator returns False when comparing two @@ -1059,6 +1147,18 @@ self.assertTrue(a != b) self.assertTrue(b != a) + def test_not_equal_on_not_equal_auth_tag(self): + """ + Test that the inequality operator returns True when comparing two + Encrypt response payloads with different authenticated encryption tag + values. + """ + a = payloads.EncryptResponsePayload(auth_tag=b'\x22') + b = payloads.EncryptResponsePayload(auth_tag=b'\xAA') + + self.assertTrue(a != b) + self.assertTrue(b != a) + def test_not_equal_on_type_mismatch(self): """ Test that the inequality operator returns True when comparing two @@ -1077,13 +1177,15 @@ payload = payloads.EncryptResponsePayload( unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_tag=b'\x01\x90\xFE' ) expected = ( "EncryptResponsePayload(" "unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', " "data=" + str(b'\x01\x23\x45\x67\x89\xAB\xCD\xEF') + ", " - "iv_counter_nonce=" + str(b'\x01') + ")" + "iv_counter_nonce=" + str(b'\x01') + ", " + "auth_tag=" + str(b'\x01\x90\xFE') + ")" ) observed = repr(payload) @@ -1096,13 +1198,15 @@ payload = payloads.EncryptResponsePayload( unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959', data=b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - iv_counter_nonce=b'\x01' + iv_counter_nonce=b'\x01', + auth_tag=b'\x01\x90\xFE' ) expected = str({ 'unique_identifier': 'b4faee10-aa2a-4446-8ad4-0881f3422959', 'data': b'\x01\x23\x45\x67\x89\xAB\xCD\xEF', - 'iv_counter_nonce': b'\x01' + 'iv_counter_nonce': b'\x01', + 'auth_tag': b'\x01\x90\xFE' }) observed = str(payload) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_get_attributes.py python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_get_attributes.py --- python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_get_attributes.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_get_attributes.py 2020-02-25 16:05:27.000000000 +0000 @@ -989,15 +989,18 @@ attribute_name=objects.Attribute.AttributeName( 'Object Group' ), - attribute_value=attributes.ObjectGroup('Group1') + attribute_value=primitives.TextString( + "Group1", + enums.Tags.OBJECT_GROUP + ) ), objects.Attribute( attribute_name=objects.Attribute.AttributeName( 'Application Specific Information' ), attribute_value=attributes.ApplicationSpecificInformation( - attributes.ApplicationNamespace('ssl'), - attributes.ApplicationData('www.example.com') + application_namespace="ssl", + application_data="www.example.com" ) ), objects.Attribute( @@ -1166,8 +1169,8 @@ 'Application Specific Information' ), attribute_value=attributes.ApplicationSpecificInformation( - attributes.ApplicationNamespace('ssl'), - attributes.ApplicationData('www.example.com') + application_namespace="ssl", + application_data="www.example.com" ) ), payload.attributes @@ -1271,8 +1274,8 @@ 'Application Specific Information' ), attribute_value=attributes.ApplicationSpecificInformation( - attributes.ApplicationNamespace('ssl'), - attributes.ApplicationData('www.example.com') + application_namespace="ssl", + application_data="www.example.com" ) ), objects.Attribute( diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_modify_attribute.py python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_modify_attribute.py --- python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_modify_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_modify_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,975 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools + +from kmip.core import enums +from kmip.core import exceptions +from kmip.core import objects +from kmip.core import primitives +from kmip.core import utils + +from kmip.core.messages import payloads + + +class TestModifyAttributeRequestPayload(testtools.TestCase): + """ + A unit test suite for the ModifyAttribute request payload. + """ + + def setUp(self): + super(TestModifyAttributeRequestPayload, self).setUp() + + # This encoding was taken from test case 3.1.4-6 from the KMIP 1.1 + # test suite. + # + # This encoding matches the following set of values. + # Request Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # Attribute + # Attribute Name - x-attribute1 + # Attribute Value - ModifiedValue1 + self.full_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x68' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x00\x08\x01\x00\x00\x00\x30' + b'\x42\x00\x0A\x07\x00\x00\x00\x0C' + b'\x78\x2D\x61\x74\x74\x72\x69\x62\x75\x74\x65\x31\x00\x00\x00\x00' + b'\x42\x00\x0B\x07\x00\x00\x00\x0E' + b'\x4D\x6F\x64\x69\x66\x69\x65\x64\x56\x61\x6C\x75\x65\x31\x00\x00' + ) + + # This encoding was adapted from test case 3.1.4-6 from the KMIP 1.1 + # test suite. It was modified to reflect the ModifyAttribute operation + # changes in KMIP 2.0. The attribute encoding was removed and the + # current and new attribute encodings were manually added. + # + # This encoding matches the following set of values. + # Request Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # Current Attribute + # Cryptographic Algorithm - AES + # New Attribute + # Cryptographic Algorithm - RSA + self.full_encoding_kmip_2_0 = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x60' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x01\x3C\x01\x00\x00\x00\x10' + b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00' + b'\x42\x01\x3D\x01\x00\x00\x00\x10' + b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00' + ) + + self.empty_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x00' + ) + + def tearDown(self): + super(TestModifyAttributeRequestPayload, self).tearDown() + + def test_invalid_unique_identifier(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the unique identifier of a ModifyAttribute request payload. + """ + kwargs = {"unique_identifier": 0} + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + payloads.ModifyAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.ModifyAttributeRequestPayload(), + "unique_identifier", + 0 + ) + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + setattr, + *args + ) + + def test_invalid_attribute(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the attribute of a ModifyAttribute request payload. + """ + kwargs = {"attribute": "invalid"} + self.assertRaisesRegex( + TypeError, + "The attribute must be an Attribute object.", + payloads.ModifyAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.ModifyAttributeRequestPayload(), + "attribute", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The attribute must be an Attribute object.", + setattr, + *args + ) + + def test_invalid_current_attribute(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the current attribute of a ModifyAttribute request payload. + """ + kwargs = {"current_attribute": "invalid"} + self.assertRaisesRegex( + TypeError, + "The current attribute must be a CurrentAttribute object.", + payloads.ModifyAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.ModifyAttributeRequestPayload(), + "current_attribute", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The current attribute must be a CurrentAttribute object.", + setattr, + *args + ) + + def test_invalid_new_attribute(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the new attribute of a ModifyAttribute request payload. + """ + kwargs = {"new_attribute": "invalid"} + self.assertRaisesRegex( + TypeError, + "The new attribute must be a NewAttribute object.", + payloads.ModifyAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.ModifyAttributeRequestPayload(), + "new_attribute", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The new attribute must be a NewAttribute object.", + setattr, + *args + ) + + def test_read(self): + """ + Test that a ModifyAttribute request payload can be read from a buffer. + """ + payload = payloads.ModifyAttributeRequestPayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.new_attribute) + + payload.read(self.full_encoding) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertEqual( + objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ), + payload.attribute + ) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.new_attribute) + + def test_read_kmip_2_0(self): + """ + Test that a ModifyAttribute request payload can be read from a buffer + with KMIP 2.0 fields. + """ + payload = payloads.ModifyAttributeRequestPayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute) + self.assertIsNone(payload.current_attribute) + self.assertIsNone(payload.new_attribute) + + payload.read( + self.full_encoding_kmip_2_0, + kmip_version=enums.KMIPVersion.KMIP_2_0 + ) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertIsNone(payload.attribute) + self.assertEqual( + objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + payload.current_attribute + ) + self.assertEqual( + objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.RSA, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + payload.new_attribute + ) + + def test_read_no_attribute(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded attribute is used to decode a + ModifyAttribute request payload. + """ + payload = payloads.ModifyAttributeRequestPayload() + args = (self.empty_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The ModifyAttribute request payload encoding is missing the " + "attribute field.", + payload.read, + *args + ) + + def test_read_no_new_attribute(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded new attribute is used to decode a + ModifyAttribute request payload. + """ + payload = payloads.ModifyAttributeRequestPayload() + args = (self.empty_encoding, ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_2_0} + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The ModifyAttribute request payload encoding is missing the " + "new attribute field.", + payload.read, + *args, + **kwargs + ) + + def test_write(self): + """ + Test that a ModifyAttribute request payload can be written to a buffer. + """ + payload = payloads.ModifyAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + + buffer = utils.BytearrayStream() + payload.write(buffer) + + self.assertEqual(len(self.full_encoding), len(buffer)) + self.assertEqual(str(self.full_encoding), str(buffer)) + + def test_write_kmip_2_0(self): + """ + Test that a ModifyAttribute request payload can be written to a buffer + with KMIP 2.0 fields. + """ + payload = payloads.ModifyAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + current_attribute=objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.RSA, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + + buffer = utils.BytearrayStream() + payload.write(buffer, kmip_version=enums.KMIPVersion.KMIP_2_0) + + self.assertEqual(len(self.full_encoding_kmip_2_0), len(buffer)) + self.assertEqual(str(self.full_encoding_kmip_2_0), str(buffer)) + + def test_write_no_attribute(self): + """ + Test that an InvalidField error is raised when attempting to write a + ModifyAttribute request payload to a buffer with no attribute field + specified. + """ + payload = payloads.ModifyAttributeRequestPayload() + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The ModifyAttribute request payload is missing the attribute " + "field.", + payload.write, + *args + ) + + def test_write_no_new_attribute(self): + """ + Test that an InvalidField error is raised when attempting to write a + ModifyAttribute request payload to a buffer with no new attribute + field specified. + """ + payload = payloads.ModifyAttributeRequestPayload() + args = (utils.BytearrayStream(), ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_2_0} + self.assertRaisesRegex( + exceptions.InvalidField, + "The ModifyAttribute request payload is missing the new attribute " + "field.", + payload.write, + *args, + **kwargs + ) + + def test_repr(self): + """ + Test that repr can be applied to a ModifyAttribute request payload. + """ + payload = payloads.ModifyAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + + args = [ + "unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959'", + "attribute=None", + "current_attribute=None", + "new_attribute=None" + ] + self.assertEqual( + "ModifyAttributeRequestPayload({})".format(", ".join(args)), + repr(payload) + ) + + def test_str(self): + """ + Test that str can be applied to a ModifyAttribute request payload. + """ + payload = payloads.ModifyAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + s = str( + { + "unique_identifier": "b4faee10-aa2a-4446-8ad4-0881f3422959", + "attribute": None, + "current_attribute": None, + "new_attribute": None + } + ) + self.assertEqual(s, str(payload)) + + def test_comparison(self): + """ + Test that the equality/inequality operators return True/False when + comparing two ModifyAttribute request payloads with the same data. + """ + a = payloads.ModifyAttributeRequestPayload() + b = payloads.ModifyAttributeRequestPayload() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = payloads.ModifyAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ), + current_attribute=objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.RSA, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + b = payloads.ModifyAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ), + current_attribute=objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.RSA, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_unique_identifiers(self): + """ + Test that the equality/inequality operators return False/True when + comparing two ModifyAttribute request payloads with different unique + identifiers. + """ + a = payloads.ModifyAttributeRequestPayload(unique_identifier="1") + b = payloads.ModifyAttributeRequestPayload(unique_identifier="2") + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_attributes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two ModifyAttribute request payloads with different + attributes. + """ + a = payloads.ModifyAttributeRequestPayload( + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + b = payloads.ModifyAttributeRequestPayload( + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute2"), + attribute_value=primitives.TextString( + value="ModifiedValue2", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_current_attributes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two ModifyAttribute request payloads with different current + attributes. + """ + a = payloads.ModifyAttributeRequestPayload( + current_attribute=objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + b = payloads.ModifyAttributeRequestPayload( + current_attribute=objects.CurrentAttribute( + attribute=primitives.Integer( + 128, + enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_new_attributes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two ModifyAttribute request payloads with different new + attributes. + """ + a = payloads.ModifyAttributeRequestPayload( + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + b = payloads.ModifyAttributeRequestPayload( + new_attribute=objects.NewAttribute( + attribute=primitives.Integer( + 128, + enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparining a ModifyAttribute request payload against a different type. + """ + a = payloads.ModifyAttributeRequestPayload() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + +class TestModifyAttributeResponsePayload(testtools.TestCase): + """ + A unit test suite for the ModifyAttribute response payload. + """ + + def setUp(self): + super(TestModifyAttributeResponsePayload, self).setUp() + + # This encoding was taken from test case 3.1.4-6 from the KMIP 1.1 + # test suite. + # + # This encoding matches the following set of values: + # Response Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # Attribute + # Attribute Name - x-attribute1 + # Attribute Value - ModifiedValue1 + self.full_encoding = utils.BytearrayStream( + b'\x42\x00\x7C\x01\x00\x00\x00\x68' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x00\x08\x01\x00\x00\x00\x30' + b'\x42\x00\x0A\x07\x00\x00\x00\x0C' + b'\x78\x2D\x61\x74\x74\x72\x69\x62\x75\x74\x65\x31\x00\x00\x00\x00' + b'\x42\x00\x0B\x07\x00\x00\x00\x0E' + b'\x4D\x6F\x64\x69\x66\x69\x65\x64\x56\x61\x6C\x75\x65\x31\x00\x00' + ) + + # This encoding was adapted from test case 3.1.4-6 from the KMIP 1.1 + # test suite. The attribute encoding was removed. + # + # This encoding matches the following set of values: + # Response Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + self.full_encoding_kmip_2_0 = utils.BytearrayStream( + b'\x42\x00\x7C\x01\x00\x00\x00\x30' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + ) + + self.empty_encoding = utils.BytearrayStream( + b'\x42\x00\x7C\x01\x00\x00\x00\x00' + ) + + def tearDown(self): + super(TestModifyAttributeResponsePayload, self).tearDown() + + def test_invalid_unique_identifier(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the unique identifier of a ModifyAttribute response payload. + """ + kwargs = {"unique_identifier": 0} + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + payloads.ModifyAttributeResponsePayload, + **kwargs + ) + + args = ( + payloads.ModifyAttributeResponsePayload(), + "unique_identifier", + 0 + ) + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + setattr, + *args + ) + + def test_invalid_attribute(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the attribute of a ModifyAttribute response payload. + """ + kwargs = {"attribute": "invalid"} + self.assertRaisesRegex( + TypeError, + "The attribute must be an Attribute object.", + payloads.ModifyAttributeResponsePayload, + **kwargs + ) + + args = ( + payloads.ModifyAttributeResponsePayload(), + "attribute", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The attribute must be an Attribute object.", + setattr, + *args + ) + + def test_read(self): + """ + Test that a ModifyAttribute response payload can be read from a buffer. + """ + payload = payloads.ModifyAttributeResponsePayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute) + + payload.read(self.full_encoding) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertEqual( + objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ), + payload.attribute + ) + + def test_read_kmip_2_0(self): + """ + Test that a ModifyAttribute response payload can be read from a buffer. + """ + payload = payloads.ModifyAttributeResponsePayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.attribute) + + payload.read( + self.full_encoding_kmip_2_0, + kmip_version=enums.KMIPVersion.KMIP_2_0 + ) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertIsNone(payload.attribute) + + def test_read_no_unique_identifier(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded unique identifier is used to decode + a ModifyAttribute response payload. + """ + payload = payloads.ModifyAttributeResponsePayload() + args = (self.empty_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The ModifyAttribute response payload encoding is missing the " + "unique identifier field.", + payload.read, + *args + ) + + def test_read_no_attribute(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded attribute is used to decode a + ModifyAttribute response payload. + """ + payload = payloads.ModifyAttributeResponsePayload() + args = (self.full_encoding_kmip_2_0, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The ModifyAttribute response payload encoding is missing the " + "attribute field.", + payload.read, + *args + ) + + def test_write(self): + """ + Test that a ModifyAttribute response payload can be written to a + buffer. + """ + payload = payloads.ModifyAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + + buffer = utils.BytearrayStream() + payload.write(buffer) + + self.assertEqual(len(self.full_encoding), len(buffer)) + self.assertEqual(str(self.full_encoding), str(buffer)) + + def test_write_kmip_2_0(self): + """ + Test that a ModifyAttribute response payload can be written to a + buffer with KMIP 2.0 fields. + """ + payload = payloads.ModifyAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + + buffer = utils.BytearrayStream() + payload.write(buffer, kmip_version=enums.KMIPVersion.KMIP_2_0) + + self.assertEqual(len(self.full_encoding_kmip_2_0), len(buffer)) + self.assertEqual(str(self.full_encoding_kmip_2_0), str(buffer)) + + def test_write_no_unique_identifier(self): + """ + Test that an InvalidField error is raised when attempting to write + a ModifyAttribute response payload to a buffer with no unique + identifier field specified. + """ + payload = payloads.ModifyAttributeResponsePayload() + + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The ModifyAttribute response payload is missing the unique " + "identifier field.", + payload.write, + *args + ) + + def test_write_no_attribute(self): + """ + Test that an InvalidField error is raised when attempting to write + a ModifyAttribute response payload to a buffer with no unique + identifier field specified. + """ + payload = payloads.ModifyAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The ModifyAttribute response payload is missing the attribute " + "field.", + payload.write, + *args + ) + + def test_repr(self): + """ + Test that repr can be applied to a ModifyAttribute response payload. + """ + payload = payloads.ModifyAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + + args = [ + "unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959'", + "attribute=None" + ] + self.assertEqual( + "ModifyAttributeResponsePayload({})".format(", ".join(args)), + repr(payload) + ) + + def test_str(self): + """ + Test that str can be applied to a ModifyAttribute response payload. + """ + payload = payloads.ModifyAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + s = str( + { + "unique_identifier": "b4faee10-aa2a-4446-8ad4-0881f3422959", + "attribute": None + } + ) + self.assertEqual(s, str(payload)) + + def test_comparison(self): + """ + Test that the equality/inequality operators return True/False when + comparing two ModifyAttribute response payloads with the same data. + """ + a = payloads.ModifyAttributeResponsePayload() + b = payloads.ModifyAttributeResponsePayload() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = payloads.ModifyAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + b = payloads.ModifyAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + ) + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_unique_identifiers(self): + """ + Test that the equality/inequality operators return False/True when + comparing two ModifyAttribute response payloads with different unique + identifiers. + """ + a = payloads.ModifyAttributeResponsePayload(unique_identifier="1") + b = payloads.ModifyAttributeResponsePayload(unique_identifier="2") + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_attributes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two ModifyAttribute response payloads with different + attributes. + """ + a = payloads.ModifyAttributeResponsePayload( + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute1"), + attribute_value=primitives.TextString( + value="ModifiedValue1", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + )) + b = payloads.ModifyAttributeResponsePayload( + attribute=objects.Attribute( + attribute_name=objects.Attribute.AttributeName("x-attribute2"), + attribute_value=primitives.TextString( + value="ModifiedValue2", + tag=enums.Tags.ATTRIBUTE_VALUE + ) + )) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparining a ModifyAttribute response payload against a different + type. + """ + a = payloads.ModifyAttributeResponsePayload() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_set_attribute.py python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_set_attribute.py --- python-pykmip-0.9.1/kmip/tests/unit/core/messages/payloads/test_set_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/messages/payloads/test_set_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,645 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools + +from kmip.core import enums +from kmip.core import exceptions +from kmip.core import objects +from kmip.core import primitives +from kmip.core import utils + +from kmip.core.messages import payloads + + +class TestSetAttributeRequestPayload(testtools.TestCase): + """ + A unit test suite for the SetAttribute request payload. + """ + + def setUp(self): + super(TestSetAttributeRequestPayload, self).setUp() + + # This encoding was adapted from test case 3.1.4-7 from the KMIP 1.1 + # test suite. It was modified to reflect the new SetAttribute operation + # in KMIP 2.0. The new attribute was manually added. + # + # This encoding matches the following set of values. + # Request Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + # New Attribute + # Cryptographic Algorithm - AES + self.full_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x48' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + b'\x42\x01\x3D\x01\x00\x00\x00\x10' + b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00' + ) + + # This encoding was adapted from test case 3.1.4-7 from the KMIP 1.1 + # test suite. It was modified to reflect the new SetAttribute operation + # in KMIP 2.0. The new attribute was manually added and the unique + # identifier was removed. + # + # This encoding matches the following set of values. + # Request Payload + # New Attribute + # Cryptographic Algorithm - AES + self.no_unique_identifier_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x18' + b'\x42\x01\x3D\x01\x00\x00\x00\x10' + b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00' + ) + + self.empty_encoding = utils.BytearrayStream( + b'\x42\x00\x79\x01\x00\x00\x00\x00' + ) + + def tearDown(self): + super(TestSetAttributeRequestPayload, self).tearDown() + + def test_invalid_unique_identifier(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the unique identifier of a SetAttribute request payload. + """ + kwargs = {"unique_identifier": 0} + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + payloads.SetAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.SetAttributeRequestPayload(), + "unique_identifier", + 0 + ) + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + setattr, + *args + ) + + def test_invalid_new_attribute(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the new attribute of a SetAttribute request payload. + """ + kwargs = {"new_attribute": "invalid"} + self.assertRaisesRegex( + TypeError, + "The new attribute must be a NewAttribute object.", + payloads.SetAttributeRequestPayload, + **kwargs + ) + + args = ( + payloads.SetAttributeRequestPayload(), + "new_attribute", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The new attribute must be a NewAttribute object.", + setattr, + *args + ) + + def test_read(self): + """ + Test that a SetAttribute request payload can be read from a buffer. + """ + payload = payloads.SetAttributeRequestPayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.new_attribute) + + payload.read(self.full_encoding) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + self.assertEqual( + objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + payload.new_attribute + ) + + def test_read_no_unique_identifier(self): + """ + Test that a SetAttribute request payload can be read from a buffer + even when the encoding is missing the unique identifier field. + """ + payload = payloads.SetAttributeRequestPayload() + + self.assertIsNone(payload.unique_identifier) + self.assertIsNone(payload.new_attribute) + + payload.read(self.no_unique_identifier_encoding) + + self.assertIsNone(payload.unique_identifier) + self.assertEqual( + objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ), + payload.new_attribute + ) + + def test_read_no_new_attribute(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded new attribute is used to decode + a SetAttribute request payload. + """ + payload = payloads.SetAttributeRequestPayload() + args = (self.empty_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The SetAttribute request payload encoding is missing the " + "new attribute field.", + payload.read, + *args + ) + + def test_read_invalid_kmip_version(self): + """ + Test that a VersionNotSupported error is raised when an unsupported + version of KMIP is used to decode the SetAttribute request payload. + """ + payload = payloads.SetAttributeRequestPayload() + args = (self.empty_encoding, ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_1_0} + self.assertRaisesRegex( + exceptions.VersionNotSupported, + "KMIP 1.0 does not support the SetAttribute operation.", + payload.read, + *args, + **kwargs + ) + + def test_write(self): + """ + Test that a SetAttribute request payload can be written to a buffer. + """ + payload = payloads.SetAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + + buffer = utils.BytearrayStream() + payload.write(buffer) + + self.assertEqual(len(self.full_encoding), len(buffer)) + self.assertEqual(str(self.full_encoding), str(buffer)) + + def test_write_no_unique_identifier(self): + """ + Test that a SetAttribute request payload can be written to a buffer + without the unique identifier field. + """ + payload = payloads.SetAttributeRequestPayload( + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + + buffer = utils.BytearrayStream() + payload.write(buffer) + + self.assertEqual(len(self.no_unique_identifier_encoding), len(buffer)) + self.assertEqual(str(self.no_unique_identifier_encoding), str(buffer)) + + def test_write_no_new_attribute(self): + """ + Test that an InvalidField error is raised when attempting to write + a SetAttribute request payload to a buffer with no new attribute + field specified. + """ + payload = payloads.SetAttributeRequestPayload() + + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The SetAttribute request payload is missing the new attribute " + "field.", + payload.write, + *args + ) + + def test_write_invalid_kmip_version(self): + """ + Test that a VersionNotSupported error is raised when an unsupported + version of KMIP is used to encode the SetAttribute request payload. + """ + payload = payloads.SetAttributeRequestPayload() + + args = (utils.BytearrayStream(), ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_1_0} + self.assertRaisesRegex( + exceptions.VersionNotSupported, + "KMIP 1.0 does not support the SetAttribute operation.", + payload.write, + *args, + **kwargs + ) + + def test_repr(self): + """ + Test that repr can be applied to a SetAttribute request payload. + """ + payload = payloads.SetAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + new_attribute=None + ) + + args = [ + "unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959'", + "new_attribute=None" + ] + self.assertEqual( + "SetAttributeRequestPayload({})".format(", ".join(args)), + repr(payload) + ) + + def test_str(self): + """ + Test that str can be applied to a SetAttribute request payload. + """ + payload = payloads.SetAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + new_attribute=None + ) + s = str( + { + "unique_identifier": "b4faee10-aa2a-4446-8ad4-0881f3422959", + "new_attribute": None + } + ) + self.assertEqual(s, str(payload)) + + def test_comparison(self): + """ + Test that the equality/inequality operators return True/False when + comparing two SetAttribute request payloads with the same data. + """ + a = payloads.SetAttributeRequestPayload() + b = payloads.SetAttributeRequestPayload() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = payloads.SetAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + b = payloads.SetAttributeRequestPayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959", + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_unique_identifiers(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SetAttribute request payloads with different unique + identifiers. + """ + a = payloads.SetAttributeRequestPayload(unique_identifier="1") + b = payloads.SetAttributeRequestPayload(unique_identifier="2") + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_new_attributes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SetAttribute request payloads with different new + attributes. + """ + a = payloads.SetAttributeRequestPayload( + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ) + b = payloads.SetAttributeRequestPayload( + new_attribute=objects.NewAttribute( + attribute=primitives.Integer( + 128, + enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparining a SetAttribute request payload against a different type. + """ + a = payloads.SetAttributeRequestPayload() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + +class TestSetAttributeResponsePayload(testtools.TestCase): + """ + A unit test suite for the SetAttribute response payload. + """ + + def setUp(self): + super(TestSetAttributeResponsePayload, self).setUp() + + # This encoding was adapted from test case 3.1.4-7 from the KMIP 1.1 + # test suite. + # + # This encoding matches the following set of values: + # Response Payload + # Unique Identifier - b4faee10-aa2a-4446-8ad4-0881f3422959 + self.full_encoding = utils.BytearrayStream( + b'\x42\x00\x7C\x01\x00\x00\x00\x30' + b'\x42\x00\x94\x07\x00\x00\x00\x24' + b'\x62\x34\x66\x61\x65\x65\x31\x30\x2D\x61\x61\x32\x61\x2D\x34\x34' + b'\x34\x36\x2D\x38\x61\x64\x34\x2D\x30\x38\x38\x31\x66\x33\x34\x32' + b'\x32\x39\x35\x39\x00\x00\x00\x00' + ) + + self.empty_encoding = utils.BytearrayStream( + b'\x42\x00\x7C\x01\x00\x00\x00\x00' + ) + + def tearDown(self): + super(TestSetAttributeResponsePayload, self).tearDown() + + def test_invalid_unique_identifier(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the unique identifier of a SetAttribute response payload. + """ + kwargs = {"unique_identifier": 0} + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + payloads.SetAttributeResponsePayload, + **kwargs + ) + + args = ( + payloads.SetAttributeResponsePayload(), + "unique_identifier", + 0 + ) + self.assertRaisesRegex( + TypeError, + "The unique identifier must be a string.", + setattr, + *args + ) + + def test_read(self): + """ + Test that a SetAttribute response payload can be read from a buffer. + """ + payload = payloads.SetAttributeResponsePayload() + + self.assertIsNone(payload.unique_identifier) + + payload.read(self.full_encoding) + + self.assertEqual( + "b4faee10-aa2a-4446-8ad4-0881f3422959", + payload.unique_identifier + ) + + def test_read_no_unique_identifier(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing no encoded unique identifier is used to decode + a SetAttribute response payload. + """ + payload = payloads.SetAttributeResponsePayload() + args = (self.empty_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The SetAttribute response payload encoding is missing the " + "unique identifier field.", + payload.read, + *args + ) + + def test_read_invalid_kmip_version(self): + """ + Test that a VersionNotSupported error is raised when an unsupported + version of KMIP is used to decode the SetAttribute response payload. + """ + payload = payloads.SetAttributeResponsePayload() + args = (self.empty_encoding, ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_1_0} + self.assertRaisesRegex( + exceptions.VersionNotSupported, + "KMIP 1.0 does not support the SetAttribute operation.", + payload.read, + *args, + **kwargs + ) + + def test_write(self): + """ + Test that a SetAttribute response payload can be written to a + buffer. + """ + payload = payloads.SetAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + + buffer = utils.BytearrayStream() + payload.write(buffer) + + self.assertEqual(len(self.full_encoding), len(buffer)) + self.assertEqual(str(self.full_encoding), str(buffer)) + + def test_write_no_unique_identifier(self): + """ + Test that an InvalidField error is raised when attempting to write + a SetAttribute response payload to a buffer with no unique + identifier field specified. + """ + payload = payloads.SetAttributeResponsePayload() + + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The SetAttribute response payload is missing the unique " + "identifier field.", + payload.write, + *args + ) + + def test_write_invalid_kmip_version(self): + """ + Test that a VersionNotSupported error is raised when an unsupported + version of KMIP is used to encode the SetAttribute response payload. + """ + payload = payloads.SetAttributeResponsePayload() + + args = (utils.BytearrayStream(), ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_1_0} + self.assertRaisesRegex( + exceptions.VersionNotSupported, + "KMIP 1.0 does not support the SetAttribute operation.", + payload.write, + *args, + **kwargs + ) + + def test_repr(self): + """ + Test that repr can be applied to a SetAttribute response payload. + """ + payload = payloads.SetAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + + args = [ + "unique_identifier='b4faee10-aa2a-4446-8ad4-0881f3422959'" + ] + self.assertEqual( + "SetAttributeResponsePayload({})".format(", ".join(args)), + repr(payload) + ) + + def test_str(self): + """ + Test that str can be applied to a SetAttribute response payload. + """ + payload = payloads.SetAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + s = str( + { + "unique_identifier": "b4faee10-aa2a-4446-8ad4-0881f3422959" + } + ) + self.assertEqual(s, str(payload)) + + def test_comparison(self): + """ + Test that the equality/inequality operators return True/False when + comparing two SetAttribute response payloads with the same data. + """ + a = payloads.SetAttributeResponsePayload() + b = payloads.SetAttributeResponsePayload() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = payloads.SetAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + b = payloads.SetAttributeResponsePayload( + unique_identifier="b4faee10-aa2a-4446-8ad4-0881f3422959" + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_unique_identifiers(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SetAttribute response payloads with different unique + identifiers. + """ + a = payloads.SetAttributeResponsePayload(unique_identifier="1") + b = payloads.SetAttributeResponsePayload(unique_identifier="2") + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparining a SetAttribute response payload against a different + type. + """ + a = payloads.SetAttributeResponsePayload() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/messages/test_messages.py python-pykmip-0.10.0/kmip/tests/unit/core/messages/test_messages.py --- python-pykmip-0.9.1/kmip/tests/unit/core/messages/test_messages.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/messages/test_messages.py 2020-02-25 16:05:27.000000000 +0000 @@ -22,14 +22,10 @@ from kmip.core.factories.attributes import AttributeFactory from kmip.core import attributes as attr -from kmip.core.attributes import ApplicationData -from kmip.core.attributes import ApplicationNamespace -from kmip.core.attributes import ApplicationSpecificInformation from kmip.core.attributes import ContactInformation from kmip.core.attributes import CryptographicAlgorithm from kmip.core.attributes import CryptographicLength from kmip.core.attributes import Name -from kmip.core.attributes import ObjectGroup from kmip.core import enums from kmip.core.enums import AttributeType @@ -1008,7 +1004,7 @@ attributes = [] name = objects.Attribute.AttributeName('Object Group') - value = ObjectGroup('Group1') + value = TextString('Group1', tag=enums.Tags.OBJECT_GROUP) attribute = objects.Attribute(attribute_name=name, attribute_value=value) attributes.append(attribute) @@ -1017,10 +1013,10 @@ 'Information') ap_n_name = 'ssl' ap_n_value = 'www.example.com' - ap_n = ApplicationNamespace(ap_n_name) - ap_d = ApplicationData(ap_n_value) - value = ApplicationSpecificInformation(application_namespace=ap_n, - application_data=ap_d) + value = attr.ApplicationSpecificInformation( + application_namespace=ap_n_name, + application_data=ap_n_value + ) attribute = objects.Attribute(attribute_name=name, attribute_value=value) attributes.append(attribute) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/objects/test_current_attribute.py python-pykmip-0.10.0/kmip/tests/unit/core/objects/test_current_attribute.py --- python-pykmip-0.9.1/kmip/tests/unit/core/objects/test_current_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/objects/test_current_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,399 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools + +from kmip.core import enums +from kmip.core import exceptions +from kmip.core import objects +from kmip.core import primitives +from kmip.core import utils + + +class TestCurrentAttribute(testtools.TestCase): + """ + A unit test suite for the CurrentAttribute structure. + """ + + def setUp(self): + super(TestCurrentAttribute, self).setUp() + + # This encoding matches the following set of values: + # CurrentAttribute + # Cryptographic Algorithm - AES + self.full_encoding = utils.BytearrayStream( + b'\x42\x01\x3C\x01\x00\x00\x00\x10' + b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00' + ) + + self.empty_encoding = utils.BytearrayStream( + b'\x42\x01\x3C\x01\x00\x00\x00\x00' + ) + + # This encoding matches the following set of values: + # CurrentAttribute + # Non-existent Tag + self.invalid_encoding = utils.BytearrayStream( + b'\x42\x01\x3C\x01\x00\x00\x00\x10' + b'\x42\xFF\xFF\x05\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + ) + + # This encoding matches the following set of values: + # CurrentAttribute + # Operation Policy Name - b4faee10-aa2a-4446-8ad4-0881f3422959 + self.unsupported_encoding = utils.BytearrayStream( + b'\x42\x01\x3C\x01\x00\x00\x00\x30' + b'\x42\x00\x5D\x07\x00\x00\x00\x24\x62\x34\x66\x61\x65\x65\x31\x30' + b'\x2D\x61\x61\x32\x61\x2D\x34\x34\x34\x36\x2D\x38\x61\x64\x34\x2D' + b'\x30\x38\x38\x31\x66\x33\x34\x32\x32\x39\x35\x39\x00\x00\x00\x00' + ) + + def tearDown(self): + super(TestCurrentAttribute, self).tearDown() + + def test_unrecognized_attribute(self): + """ + Test that a TypeError is raised when an unrecognized attribute is used + to create a CurrentAttribute object. Note that this unrecognized + attribute is a valid PyKMIP object derived from Base, it just isn't an + attribute. + """ + kwargs = { + "attribute": primitives.Enumeration( + enums.WrappingMethod, + enums.WrappingMethod.ENCRYPT, + enums.Tags.WRAPPING_METHOD + ) + } + self.assertRaisesRegex( + TypeError, + "The attribute must be a supported attribute type.", + objects.CurrentAttribute, + **kwargs + ) + + args = ( + objects.CurrentAttribute(), + "attribute", + primitives.Enumeration( + enums.WrappingMethod, + enums.WrappingMethod.ENCRYPT, + enums.Tags.WRAPPING_METHOD + ) + ) + self.assertRaisesRegex( + TypeError, + "The attribute must be a supported attribute type.", + setattr, + *args + ) + + def test_invalid_attribute(self): + """ + Test that a TypeError is raised when an invalid value is used to + create a CurrentAttribute object. Note that the value is not a valid + PyKMIP object derived from Base and therefore cannot be an attribute. + """ + kwargs = {"attribute": "invalid"} + self.assertRaisesRegex( + TypeError, + "The attribute must be a Base object, not a {}.".format( + type("invalid") + ), + objects.CurrentAttribute, + **kwargs + ) + + args = ( + objects.CurrentAttribute(), + "attribute", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The attribute must be a Base object, not a {}.".format( + type("invalid") + ), + setattr, + *args + ) + + def test_read(self): + """ + Test that a CurrentAttribute structure can be correctly read in from + a data stream. + """ + current_attribute = objects.CurrentAttribute() + + self.assertIsNone(current_attribute.attribute) + + current_attribute.read(self.full_encoding) + + self.assertIsInstance( + current_attribute.attribute, + primitives.Enumeration + ) + self.assertEqual( + current_attribute.attribute.value, + enums.CryptographicAlgorithm.AES + ) + + def test_read_no_attribute(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding with no encoded attribute is used to decode a CurrentAttribute + object. + """ + current_attribute = objects.CurrentAttribute() + args = (self.empty_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The CurrentAttribute encoding is missing the attribute field.", + current_attribute.read, + *args + ) + + def test_read_invalid_attribute(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing an invalid attribute is used to decode a + CurrentAttribute object. + """ + current_attribute = objects.CurrentAttribute() + args = (self.invalid_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The CurrentAttribute encoding is missing the attribute field.", + current_attribute.read, + *args + ) + + def test_read_unsupported_attribute(self): + """ + Test that an AttributeNotSupported error is raised when an unsupported + attribute is parsed while reading in a CurrentAttribute object from a + data stream. This can occur when an older attribute is no longer + supported by a newer version of KMIP, or vice versa. + """ + current_attribute = objects.CurrentAttribute() + args = (self.unsupported_encoding, ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_2_0} + self.assertRaisesRegex( + exceptions.AttributeNotSupported, + "Attribute OPERATION_POLICY_NAME is not supported by KMIP 2.0.", + current_attribute.read, + *args, + **kwargs + ) + + def test_read_version_not_supported(self): + """ + Test that a VersionNotSupported error is raised when an unsupported + KMIP version is provided while reading in a CurrentAttribute object + from a data stream. The CurrentAttribute structure is only supported + in KMIP 2.0+. + """ + current_attribute = objects.CurrentAttribute() + args = (self.full_encoding, ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_1_2} + self.assertRaisesRegex( + exceptions.VersionNotSupported, + "KMIP 1.2 does not support the CurrentAttribute object.", + current_attribute.read, + *args, + **kwargs + ) + + def test_write(self): + """ + Test that a CurrentAttribute object can be written to a data stream. + """ + current_attribute = objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + + stream = utils.BytearrayStream() + current_attribute.write(stream) + + self.assertEqual(len(self.full_encoding), len(stream)) + self.assertEqual(str(self.full_encoding), str(stream)) + + def test_write_no_attribute(self): + """ + Test that an InvalidField error is raised when an empty + CurrentAttribute object is written to a data stream. + """ + current_attribute = objects.CurrentAttribute() + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The CurrentAttribute object is missing the attribute field.", + current_attribute.write, + *args + ) + + def test_write_unsupported_attribute(self): + """ + Test that an AttributeNotSupported error is raised when an unsupported + attribute is found while writing a CurrentAttribute object to a data + stream. This can occur when an older attribute is no longer supported + by a newer version of KMIP, or vice versa. + """ + current_attribute = objects.CurrentAttribute( + attribute=primitives.TextString( + "default", + tag=enums.Tags.OPERATION_POLICY_NAME + ) + ) + args = (utils.BytearrayStream(), ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_2_0} + self.assertRaisesRegex( + exceptions.AttributeNotSupported, + "Attribute OPERATION_POLICY_NAME is not supported by KMIP 2.0.", + current_attribute.write, + *args, + **kwargs + ) + + def test_write_version_not_supported(self): + """ + Test that a VersionNotSupported error is raised when an unsupported + KMIP version is provided while writing a CurrentAttribute object to + a data stream. The CurrentAttribute structure is only supported in + KMIP 2.0+. + """ + current_attribute = objects.CurrentAttribute() + args = (utils.BytearrayStream(), ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_1_4} + self.assertRaisesRegex( + exceptions.VersionNotSupported, + "KMIP 1.4 does not support the CurrentAttribute object.", + current_attribute.write, + *args, + **kwargs + ) + + def test_repr(self): + """ + Test that repr can be applied to a CurrentAttribute object. + """ + current_attribute = objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + self.assertEqual( + "CurrentAttribute(" + "attribute=Enumeration(" + "enum=CryptographicAlgorithm, " + "value=CryptographicAlgorithm.AES, " + "tag=Tags.CRYPTOGRAPHIC_ALGORITHM))", + repr(current_attribute) + ) + + def test_str(self): + """ + Test that str can be applied to a CurrentAttribute object. + """ + current_attribute = objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + self.assertEqual( + '{"attribute": Enumeration(' + 'enum=CryptographicAlgorithm, ' + 'value=CryptographicAlgorithm.AES, ' + 'tag=Tags.CRYPTOGRAPHIC_ALGORITHM)}', + str(current_attribute) + ) + + def test_comparison(self): + """ + Test that the equality/inequality operators return True/False when + comparing two CurrentAttribute objects with the same data. + """ + a = objects.CurrentAttribute() + b = objects.CurrentAttribute() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + b = objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_attributes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two CurrentAttribute objects with different attributes. + """ + a = objects.CurrentAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + b = objects.CurrentAttribute( + attribute=primitives.Integer( + 128, + enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparing two CurrentAttribute objects with different types. + """ + a = objects.CurrentAttribute() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/objects/test_new_attribute.py python-pykmip-0.10.0/kmip/tests/unit/core/objects/test_new_attribute.py --- python-pykmip-0.9.1/kmip/tests/unit/core/objects/test_new_attribute.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/objects/test_new_attribute.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,399 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools + +from kmip.core import enums +from kmip.core import exceptions +from kmip.core import objects +from kmip.core import primitives +from kmip.core import utils + + +class TestNewAttribute(testtools.TestCase): + """ + A unit test suite for the NewAttribute structure. + """ + + def setUp(self): + super(TestNewAttribute, self).setUp() + + # This encoding matches the following set of values: + # NewAttribute + # Cryptographic Algorithm - AES + self.full_encoding = utils.BytearrayStream( + b'\x42\x01\x3D\x01\x00\x00\x00\x10' + b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00' + ) + + self.empty_encoding = utils.BytearrayStream( + b'\x42\x01\x3D\x01\x00\x00\x00\x00' + ) + + # This encoding matches the following set of values: + # NewAttribute + # Non-existent Tag + self.invalid_encoding = utils.BytearrayStream( + b'\x42\x01\x3D\x01\x00\x00\x00\x10' + b'\x42\xFF\xFF\x05\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + ) + + # This encoding matches the following set of values: + # NewAttribute + # Operation Policy Name - b4faee10-aa2a-4446-8ad4-0881f3422959 + self.unsupported_encoding = utils.BytearrayStream( + b'\x42\x01\x3D\x01\x00\x00\x00\x30' + b'\x42\x00\x5D\x07\x00\x00\x00\x24\x62\x34\x66\x61\x65\x65\x31\x30' + b'\x2D\x61\x61\x32\x61\x2D\x34\x34\x34\x36\x2D\x38\x61\x64\x34\x2D' + b'\x30\x38\x38\x31\x66\x33\x34\x32\x32\x39\x35\x39\x00\x00\x00\x00' + ) + + def tearDown(self): + super(TestNewAttribute, self).tearDown() + + def test_unrecognized_attribute(self): + """ + Test that a TypeError is raised when an unrecognized attribute is used + to create a NewAttribute object. Note that this unrecognized + attribute is a valid PyKMIP object derived from Base, it just isn't an + attribute. + """ + kwargs = { + "attribute": primitives.Enumeration( + enums.WrappingMethod, + enums.WrappingMethod.ENCRYPT, + enums.Tags.WRAPPING_METHOD + ) + } + self.assertRaisesRegex( + TypeError, + "The attribute must be a supported attribute type.", + objects.NewAttribute, + **kwargs + ) + + args = ( + objects.NewAttribute(), + "attribute", + primitives.Enumeration( + enums.WrappingMethod, + enums.WrappingMethod.ENCRYPT, + enums.Tags.WRAPPING_METHOD + ) + ) + self.assertRaisesRegex( + TypeError, + "The attribute must be a supported attribute type.", + setattr, + *args + ) + + def test_invalid_attribute(self): + """ + Test that a TypeError is raised when an invalid value is used to + create a NewAttribute object. Note that the value is not a valid + PyKMIP object derived from Base and therefore cannot be an attribute. + """ + kwargs = {"attribute": "invalid"} + self.assertRaisesRegex( + TypeError, + "The attribute must be a Base object, not a {}.".format( + type("invalid") + ), + objects.NewAttribute, + **kwargs + ) + + args = ( + objects.NewAttribute(), + "attribute", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The attribute must be a Base object, not a {}.".format( + type("invalid") + ), + setattr, + *args + ) + + def test_read(self): + """ + Test that a NewAttribute structure can be correctly read in from + a data stream. + """ + new_attribute = objects.NewAttribute() + + self.assertIsNone(new_attribute.attribute) + + new_attribute.read(self.full_encoding) + + self.assertIsInstance( + new_attribute.attribute, + primitives.Enumeration + ) + self.assertEqual( + new_attribute.attribute.value, + enums.CryptographicAlgorithm.AES + ) + + def test_read_no_attribute(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding with no encoded attribute is used to decode a NewAttribute + object. + """ + new_attribute = objects.NewAttribute() + args = (self.empty_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The NewAttribute encoding is missing the attribute field.", + new_attribute.read, + *args + ) + + def test_read_invalid_attribute(self): + """ + Test that an InvalidKmipEncoding error is raised when an invalid + encoding containing an invalid attribute is used to decode a + NewAttribute object. + """ + new_attribute = objects.NewAttribute() + args = (self.invalid_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The NewAttribute encoding is missing the attribute field.", + new_attribute.read, + *args + ) + + def test_read_unsupported_attribute(self): + """ + Test that an AttributeNotSupported error is raised when an unsupported + attribute is parsed while reading in a NewAttribute object from a + data stream. This can occur when an older attribute is no longer + supported by a newer version of KMIP, or vice versa. + """ + new_attribute = objects.NewAttribute() + args = (self.unsupported_encoding, ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_2_0} + self.assertRaisesRegex( + exceptions.AttributeNotSupported, + "Attribute OPERATION_POLICY_NAME is not supported by KMIP 2.0.", + new_attribute.read, + *args, + **kwargs + ) + + def test_read_version_not_supported(self): + """ + Test that a VersionNotSupported error is raised when an unsupported + KMIP version is provided while reading in a NewAttribute object + from a data stream. The NewAttribute structure is only supported + in KMIP 2.0+. + """ + new_attribute = objects.NewAttribute() + args = (self.full_encoding, ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_1_2} + self.assertRaisesRegex( + exceptions.VersionNotSupported, + "KMIP 1.2 does not support the NewAttribute object.", + new_attribute.read, + *args, + **kwargs + ) + + def test_write(self): + """ + Test that a NewAttribute object can be written to a data stream. + """ + new_attribute = objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + + stream = utils.BytearrayStream() + new_attribute.write(stream) + + self.assertEqual(len(self.full_encoding), len(stream)) + self.assertEqual(str(self.full_encoding), str(stream)) + + def test_write_no_attribute(self): + """ + Test that an InvalidField error is raised when an empty + NewAttribute object is written to a data stream. + """ + new_attribute = objects.NewAttribute() + args = (utils.BytearrayStream(), ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The NewAttribute object is missing the attribute field.", + new_attribute.write, + *args + ) + + def test_write_unsupported_attribute(self): + """ + Test that an AttributeNotSupported error is raised when an unsupported + attribute is found while writing a NewAttribute object to a data + stream. This can occur when an older attribute is no longer supported + by a newer version of KMIP, or vice versa. + """ + new_attribute = objects.NewAttribute( + attribute=primitives.TextString( + "default", + tag=enums.Tags.OPERATION_POLICY_NAME + ) + ) + args = (utils.BytearrayStream(), ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_2_0} + self.assertRaisesRegex( + exceptions.AttributeNotSupported, + "Attribute OPERATION_POLICY_NAME is not supported by KMIP 2.0.", + new_attribute.write, + *args, + **kwargs + ) + + def test_write_version_not_supported(self): + """ + Test that a VersionNotSupported error is raised when an unsupported + KMIP version is provided while writing a NewAttribute object to + a data stream. The NewAttribute structure is only supported in + KMIP 2.0+. + """ + new_attribute = objects.NewAttribute() + args = (utils.BytearrayStream(), ) + kwargs = {"kmip_version": enums.KMIPVersion.KMIP_1_4} + self.assertRaisesRegex( + exceptions.VersionNotSupported, + "KMIP 1.4 does not support the NewAttribute object.", + new_attribute.write, + *args, + **kwargs + ) + + def test_repr(self): + """ + Test that repr can be applied to a NewAttribute object. + """ + new_attribute = objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + self.assertEqual( + "NewAttribute(" + "attribute=Enumeration(" + "enum=CryptographicAlgorithm, " + "value=CryptographicAlgorithm.AES, " + "tag=Tags.CRYPTOGRAPHIC_ALGORITHM))", + repr(new_attribute) + ) + + def test_str(self): + """ + Test that str can be applied to a NewAttribute object. + """ + new_attribute = objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + self.assertEqual( + '{"attribute": Enumeration(' + 'enum=CryptographicAlgorithm, ' + 'value=CryptographicAlgorithm.AES, ' + 'tag=Tags.CRYPTOGRAPHIC_ALGORITHM)}', + str(new_attribute) + ) + + def test_comparison(self): + """ + Test that the equality/inequality operators return True/False when + comparing two NewAttribute objects with the same data. + """ + a = objects.NewAttribute() + b = objects.NewAttribute() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + b = objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_attributes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two NewAttribute objects with different attributes. + """ + a = objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + b = objects.NewAttribute( + attribute=primitives.Integer( + 128, + enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparing two NewAttribute objects with different types. + """ + a = objects.NewAttribute() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/core/secrets/test_split_key.py python-pykmip-0.10.0/kmip/tests/unit/core/secrets/test_split_key.py --- python-pykmip-0.9.1/kmip/tests/unit/core/secrets/test_split_key.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/core/secrets/test_split_key.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,878 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools + +from kmip.core import enums +from kmip.core import exceptions +from kmip.core import misc +from kmip.core import objects +from kmip.core import primitives +from kmip.core import secrets +from kmip.core import utils + + +class TestSplitKey(testtools.TestCase): + """ + Test suite for the SplitKey secret object. + """ + + def setUp(self): + super(TestSplitKey, self).setUp() + + # This encoding was adapted from test case TC-SJ-2-20 from the KMIP + # 2.0 test suite. The Prime Field Size was manually added. + # + # This encoding matches the following set of values: + # SplitKey + # Split Key Parts - 4 + # Key Part Identifier - 1 + # Split Key Threshold - 2 + # Split Key Method - Polynomial Sharing GF 2^8 + # Prime Field Size - 104729 + # Key Block + # Key Format Type - Raw + # Key Value + # Key Material - 0x66C46A7754F94DE420C7B1A7FFF5EC56 + # Cryptographic Algorithm - AES + # Cryptographic Length - 128 + self.full_encoding = utils.BytearrayStream( + b'\x42\x00\x89\x01\x00\x00\x00\xA8' + b'\x42\x00\x8B\x02\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00' + b'\x42\x00\x44\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + b'\x42\x00\x8C\x02\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x00' + b'\x42\x00\x8A\x05\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00' + b'\x42\x00\x62\x04\x00\x00\x00\x08\x00\x00\x00\x00\x00\x01\x99\x19' + b'\x42\x00\x40\x01\x00\x00\x00\x50' + b'\x42\x00\x42\x05\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + b'\x42\x00\x45\x01\x00\x00\x00\x18' + b'\x42\x00\x43\x08\x00\x00\x00\x10' + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00' + b'\x42\x00\x2A\x02\x00\x00\x00\x04\x00\x00\x00\x80\x00\x00\x00\x00' + ) + + # This encoding was adapted from test case TC-SJ-2-20 from the KMIP + # 2.0 test suite. + # + # This encoding matches the following set of values: + # SplitKey + # Key Part Identifier - 1 + self.no_split_key_parts_encoding = utils.BytearrayStream( + b'\x42\x00\x89\x01\x00\x00\x00\x10' + b'\x42\x00\x44\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + ) + + # This encoding was adapted from test case TC-SJ-2-20 from the KMIP + # 2.0 test suite. + # + # This encoding matches the following set of values: + # SplitKey + # Split Key Parts - 4 + self.no_key_part_identifier_encoding = utils.BytearrayStream( + b'\x42\x00\x89\x01\x00\x00\x00\x10' + b'\x42\x00\x8B\x02\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00' + ) + + # This encoding was adapted from test case TC-SJ-2-20 from the KMIP + # 2.0 test suite. + # + # This encoding matches the following set of values: + # SplitKey + # Split Key Parts - 4 + # Key Part Identifier - 1 + self.no_split_key_threshold_encoding = utils.BytearrayStream( + b'\x42\x00\x89\x01\x00\x00\x00\x20' + b'\x42\x00\x8B\x02\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00' + b'\x42\x00\x44\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + ) + + # This encoding was adapted from test case TC-SJ-2-20 from the KMIP + # 2.0 test suite. + # + # This encoding matches the following set of values: + # SplitKey + # Split Key Parts - 4 + # Key Part Identifier - 1 + # Split Key Threshold - 2 + self.no_split_key_method_encoding = utils.BytearrayStream( + b'\x42\x00\x89\x01\x00\x00\x00\x30' + b'\x42\x00\x8B\x02\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00' + b'\x42\x00\x44\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + b'\x42\x00\x8C\x02\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x00' + ) + + # This encoding was adapted from test case TC-SJ-2-20 from the KMIP + # 2.0 test suite. + # + # This encoding matches the following set of values: + # SplitKey + # Split Key Parts - 4 + # Key Part Identifier - 1 + # Split Key Threshold - 2 + # Split Key Method - Polynomial Sharing Prime Field + self.no_prime_field_size_encoding = utils.BytearrayStream( + b'\x42\x00\x89\x01\x00\x00\x00\x40' + b'\x42\x00\x8B\x02\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00' + b'\x42\x00\x44\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + b'\x42\x00\x8C\x02\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x00' + b'\x42\x00\x8A\x05\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00' + ) + + # This encoding was adapted from test case TC-SJ-2-20 from the KMIP + # 2.0 test suite. + # + # This encoding matches the following set of values: + # SplitKey + # Split Key Parts - 4 + # Key Part Identifier - 1 + # Split Key Threshold - 2 + # Split Key Method - Polynomial Sharing GF 2^8 + self.no_key_block_encoding = utils.BytearrayStream( + b'\x42\x00\x89\x01\x00\x00\x00\x40' + b'\x42\x00\x8B\x02\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00' + b'\x42\x00\x44\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00' + b'\x42\x00\x8C\x02\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x00' + b'\x42\x00\x8A\x05\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00' + ) + + def tearDown(self): + super(TestSplitKey, self).tearDown() + + def test_invalid_split_key_parts(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the split key parts of a SplitKey object. + """ + kwargs = {"split_key_parts": "invalid"} + self.assertRaisesRegex( + TypeError, + "The split key parts must be an integer.", + secrets.SplitKey, + **kwargs + ) + + args = ( + secrets.SplitKey(), + "split_key_parts", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The split key parts must be an integer.", + setattr, + *args + ) + + def test_invalid_key_part_identifier(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the key part identifier of a SplitKey object. + """ + kwargs = {"key_part_identifier": "invalid"} + self.assertRaisesRegex( + TypeError, + "The key part identifier must be an integer.", + secrets.SplitKey, + **kwargs + ) + + args = ( + secrets.SplitKey(), + "key_part_identifier", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The key part identifier must be an integer.", + setattr, + *args + ) + + def test_invalid_split_key_threshold(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the split key threshold of a SplitKey object. + """ + kwargs = {"split_key_threshold": "invalid"} + self.assertRaisesRegex( + TypeError, + "The split key threshold must be an integer.", + secrets.SplitKey, + **kwargs + ) + + args = ( + secrets.SplitKey(), + "split_key_threshold", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The split key threshold must be an integer.", + setattr, + *args + ) + + def test_invalid_split_key_method(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the split key method of a SplitKey object. + """ + kwargs = {"split_key_method": "invalid"} + self.assertRaisesRegex( + TypeError, + "The split key method must be a SplitKeyMethod enumeration.", + secrets.SplitKey, + **kwargs + ) + + args = ( + secrets.SplitKey(), + "split_key_method", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The split key method must be a SplitKeyMethod enumeration.", + setattr, + *args + ) + + def test_invalid_prime_field_size(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the prime field size of a SplitKey object. + """ + kwargs = {"prime_field_size": "invalid"} + self.assertRaisesRegex( + TypeError, + "The prime field size must be an integer.", + secrets.SplitKey, + **kwargs + ) + + args = ( + secrets.SplitKey(), + "prime_field_size", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The prime field size must be an integer.", + setattr, + *args + ) + + def test_invalid_key_block(self): + """ + Test that a TypeError is raised when an invalid value is used to set + the key block of a SplitKey object. + """ + kwargs = {"key_block": "invalid"} + self.assertRaisesRegex( + TypeError, + "The key block must be a KeyBlock structure.", + secrets.SplitKey, + **kwargs + ) + + args = ( + secrets.SplitKey(), + "key_block", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The key block must be a KeyBlock structure.", + setattr, + *args + ) + + def test_read(self): + """ + Test that a SplitKey object can be read from a buffer. + """ + split_key = secrets.SplitKey() + + self.assertIsNone(split_key.split_key_parts) + self.assertIsNone(split_key.key_part_identifier) + self.assertIsNone(split_key.split_key_threshold) + self.assertIsNone(split_key.split_key_method) + self.assertIsNone(split_key.prime_field_size) + self.assertIsNone(split_key.key_block) + + split_key.read(self.full_encoding) + + self.assertEqual(4, split_key.split_key_parts) + self.assertEqual(1, split_key.key_part_identifier) + self.assertEqual(2, split_key.split_key_threshold) + self.assertEqual( + enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + split_key.split_key_method + ) + self.assertEqual(104729, split_key.prime_field_size) + self.assertIsInstance(split_key.key_block, objects.KeyBlock) + self.assertEqual( + enums.KeyFormatType.RAW, + split_key.key_block.key_format_type.value + ) + self.assertIsInstance(split_key.key_block.key_value, objects.KeyValue) + self.assertIsInstance( + split_key.key_block.key_value.key_material, + primitives.ByteString + ) + self.assertEqual( + ( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ), + split_key.key_block.key_value.key_material.value + ) + self.assertEqual( + enums.CryptographicAlgorithm.AES, + split_key.key_block.cryptographic_algorithm.value + ) + self.assertEqual(128, split_key.key_block.cryptographic_length.value) + + def test_read_missing_split_key_parts(self): + """ + Test that an InvalidKmipEncoding error is raised during the decoding + of a SplitKey object when the split key parts are missing from the + encoding. + """ + split_key = secrets.SplitKey() + + self.assertIsNone(split_key.split_key_parts) + + args = (self.no_split_key_parts_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The SplitKey encoding is missing the SplitKeyParts field.", + split_key.read, + *args + ) + + def test_read_missing_key_part_identifier(self): + """ + Test that an InvalidKmipEncoding error is raised during the decoding + of a SplitKey object when the key part identifier is missing from the + encoding. + """ + split_key = secrets.SplitKey() + + self.assertIsNone(split_key.key_part_identifier) + + args = (self.no_key_part_identifier_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The SplitKey encoding is missing the KeyPartIdentifier field.", + split_key.read, + *args + ) + + def test_read_missing_split_key_threshold(self): + """ + Test that an InvalidKmipEncoding error is raised during the decoding + of a SplitKey object when the split key threshold is missing from the + encoding. + """ + split_key = secrets.SplitKey() + + self.assertIsNone(split_key.split_key_threshold) + + args = (self.no_split_key_threshold_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The SplitKey encoding is missing the SplitKeyThreshold field.", + split_key.read, + *args + ) + + def test_read_missing_split_key_method(self): + """ + Test that an InvalidKmipEncoding error is raised during the decoding + of a SplitKey object when the split key method is missing from the + encoding. + """ + split_key = secrets.SplitKey() + + self.assertIsNone(split_key.split_key_method) + + args = (self.no_split_key_method_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The SplitKey encoding is missing the SplitKeyMethod field.", + split_key.read, + *args + ) + + def test_read_missing_prime_field_size(self): + """ + Test that an InvalidKmipEncoding error is raised during the decoding + of a SplitKey object when the prime field size is missing from the + encoding. + """ + split_key = secrets.SplitKey() + + self.assertIsNone(split_key.prime_field_size) + + args = (self.no_prime_field_size_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The SplitKey encoding is missing the PrimeFieldSize " + "field. This field is required when the SplitKeyMethod is " + "PolynomialSharingPrimeField.", + split_key.read, + *args + ) + + def test_read_missing_key_block(self): + """ + Test that an InvalidKmipEncoding error is raised during the decoding + of a SplitKey object when the key block is missing from the encoding. + """ + split_key = secrets.SplitKey() + + self.assertIsNone(split_key.key_block) + + args = (self.no_key_block_encoding, ) + self.assertRaisesRegex( + exceptions.InvalidKmipEncoding, + "The SplitKey encoding is missing the KeyBlock field.", + split_key.read, + *args + ) + + def test_write(self): + """ + Test that a SplitKey object can be written to a buffer. + """ + # TODO (peter-hamilton) Update this test when the KeyBlock supports + # generic key format type and key value/material values. + key_block = objects.KeyBlock( + key_format_type=misc.KeyFormatType(enums.KeyFormatType.RAW), + key_value=objects.KeyValue( + key_material=objects.KeyMaterial( + value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ) + ) + ), + cryptographic_algorithm=primitives.Enumeration( + enums.CryptographicAlgorithm, + value=enums.CryptographicAlgorithm.AES, + tag=enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ), + cryptographic_length=primitives.Integer( + value=128, + tag=enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + split_key = secrets.SplitKey( + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729, + key_block=key_block + ) + + stream = utils.BytearrayStream() + split_key.write(stream) + + self.assertEqual(len(self.full_encoding), len(stream)) + self.assertEqual(str(self.full_encoding), str(stream)) + + def test_write_missing_split_key_parts(self): + """ + Test that an InvalidField error is raised during the encoding of a + SplitKey object when the object is missing the split key parts field. + """ + split_key = secrets.SplitKey(key_part_identifier=1) + + stream = utils.BytearrayStream() + args = (stream, ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The SplitKey object is missing the SplitKeyParts field.", + split_key.write, + *args + ) + + def test_write_missing_key_part_identifier(self): + """ + Test that an InvalidField error is raised during the encoding of a + SplitKey object when the object is missing the key part identifier + field. + """ + split_key = secrets.SplitKey(split_key_parts=4) + + stream = utils.BytearrayStream() + args = (stream, ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The SplitKey object is missing the KeyPartIdentifier field.", + split_key.write, + *args + ) + + def test_write_missing_split_key_threshold(self): + """ + Test that an InvalidField error is raised during the encoding of a + SplitKey object when the object is missing the split key threshold + field. + """ + split_key = secrets.SplitKey( + split_key_parts=4, + key_part_identifier=1 + ) + + stream = utils.BytearrayStream() + args = (stream, ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The SplitKey object is missing the SplitKeyThreshold field.", + split_key.write, + *args + ) + + def test_write_missing_split_key_method(self): + """ + Test that an InvalidField error is raised during the encoding of a + SplitKey object when the object is missing the split key method field. + """ + split_key = secrets.SplitKey( + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2 + ) + + stream = utils.BytearrayStream() + args = (stream, ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The SplitKey object is missing the SplitKeyMethod field.", + split_key.write, + *args + ) + + def test_write_missing_prime_field_size(self): + """ + Test that an InvalidField error is raised during the encoding of a + SplitKey object when the object is missing the prime field size field. + """ + split_key_method = enums.SplitKeyMethod.POLYNOMIAL_SHARING_PRIME_FIELD + split_key = secrets.SplitKey( + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=split_key_method + ) + + stream = utils.BytearrayStream() + args = (stream, ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The SplitKey object is missing the PrimeFieldSize field. " + "This field is required when the SplitKeyMethod is " + "PolynomialSharingPrimeField.", + split_key.write, + *args + ) + + def test_write_missing_key_block(self): + """ + Test that an InvalidField error is raised during the encoding of a + SplitKey object when the object is missing the key block field. + """ + split_key = secrets.SplitKey( + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8 + ) + + stream = utils.BytearrayStream() + args = (stream, ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The SplitKey object is missing the KeyBlock field.", + split_key.write, + *args + ) + + def test_repr(self): + """ + Test that repr can be applied to a SplitKey object. + """ + key_block = objects.KeyBlock( + key_format_type=misc.KeyFormatType(enums.KeyFormatType.RAW), + key_value=objects.KeyValue( + key_material=objects.KeyMaterial( + value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ) + ) + ), + cryptographic_algorithm=primitives.Enumeration( + enums.CryptographicAlgorithm, + value=enums.CryptographicAlgorithm.AES, + tag=enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ), + cryptographic_length=primitives.Integer( + value=128, + tag=enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + split_key = secrets.SplitKey( + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729, + key_block=key_block + ) + + args = [ + "split_key_parts=4", + "key_part_identifier=1", + "split_key_threshold=2", + "split_key_method=SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8", + "prime_field_size=104729", + "key_block=Struct()" + ] + self.assertEqual( + "SplitKey({})".format(", ".join(args)), + repr(split_key) + ) + + def test_str(self): + """ + Test that str can be applied to a SplitKey object. + """ + key_block = objects.KeyBlock( + key_format_type=misc.KeyFormatType(enums.KeyFormatType.RAW), + key_value=objects.KeyValue( + key_material=objects.KeyMaterial( + value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ) + ) + ), + cryptographic_algorithm=primitives.Enumeration( + enums.CryptographicAlgorithm, + value=enums.CryptographicAlgorithm.AES, + tag=enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ), + cryptographic_length=primitives.Integer( + value=128, + tag=enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + split_key = secrets.SplitKey( + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729, + key_block=key_block + ) + + args = [ + ("split_key_parts", 4), + ("key_part_identifier", 1), + ("split_key_threshold", 2), + ( + "split_key_method", + enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8 + ), + ("prime_field_size", 104729), + ("key_block", str(key_block)) + ] + value = "{}".format( + ", ".join(['"{}": {}'.format(arg[0], arg[1]) for arg in args]) + ) + self.assertEqual( + "{" + value + "}", + str(split_key) + ) + + def test_comparison(self): + """ + Test that the equality/inequality operators return True/False when + comparing two SplitKey objects with the same data. + """ + a = secrets.SplitKey() + b = secrets.SplitKey() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = secrets.SplitKey( + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729, + key_block=objects.KeyBlock( + key_format_type=misc.KeyFormatType(enums.KeyFormatType.RAW), + key_value=objects.KeyValue( + key_material=objects.KeyMaterial( + value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ) + ) + ), + cryptographic_algorithm=primitives.Enumeration( + enums.CryptographicAlgorithm, + value=enums.CryptographicAlgorithm.AES, + tag=enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ), + cryptographic_length=primitives.Integer( + value=128, + tag=enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + ) + b = secrets.SplitKey( + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729, + key_block=objects.KeyBlock( + key_format_type=misc.KeyFormatType(enums.KeyFormatType.RAW), + key_value=objects.KeyValue( + key_material=objects.KeyMaterial( + value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ) + ) + ), + cryptographic_algorithm=primitives.Enumeration( + enums.CryptographicAlgorithm, + value=enums.CryptographicAlgorithm.AES, + tag=enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ), + cryptographic_length=primitives.Integer( + value=128, + tag=enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_split_key_parts(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different split key parts. + """ + a = secrets.SplitKey(split_key_parts=4) + b = secrets.SplitKey(split_key_parts=6) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_key_part_identifiers(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different key part identifiers. + """ + a = secrets.SplitKey(key_part_identifier=1) + b = secrets.SplitKey(key_part_identifier=2) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_split_key_thresholds(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different split key thresholds. + """ + a = secrets.SplitKey(split_key_threshold=3) + b = secrets.SplitKey(split_key_threshold=4) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_split_key_methods(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different split key methods. + """ + a = secrets.SplitKey(split_key_method=enums.SplitKeyMethod.XOR) + b = secrets.SplitKey( + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8 + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_prime_field_sizes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different prime field sizes. + """ + a = secrets.SplitKey(prime_field_size=104723) + b = secrets.SplitKey(prime_field_size=104729) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + # TODO (peter-hamilton) Fill in this test once the KeyBlock supports the + # comparison operators. + def test_comparison_on_different_key_blocks(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different key blocks. + """ + self.skipTest( + "The KeyBlock structure does not support the comparison operators." + ) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different types. + """ + a = secrets.SplitKey() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/pie/objects/test_application_specific_information.py python-pykmip-0.10.0/kmip/tests/unit/pie/objects/test_application_specific_information.py --- python-pykmip-0.9.1/kmip/tests/unit/pie/objects/test_application_specific_information.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/pie/objects/test_application_specific_information.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,263 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sqlalchemy +import testtools + +from kmip.pie import objects +from kmip.pie import sqltypes + + +class TestApplicationSpecificInformation(testtools.TestCase): + """ + Test suite for ApplicationSpecificInformation. + """ + + def setUp(self): + super(TestApplicationSpecificInformation, self).setUp() + + def tearDown(self): + super(TestApplicationSpecificInformation, self).tearDown() + + def test_init(self): + """ + Test that an ApplicationSpecificInformation object can be instantiated. + """ + app_specific_info = objects.ApplicationSpecificInformation() + + self.assertIsNone(app_specific_info.application_namespace) + self.assertIsNone(app_specific_info.application_data) + + def test_invalid_application_namespace(self): + """ + Test that a TypeError is raised when an invalid application namespace + value is used to construct an ApplicationSpecificInformation attribute. + """ + kwargs = {"application_namespace": []} + self.assertRaisesRegex( + TypeError, + "The application namespace must be a string.", + objects.ApplicationSpecificInformation, + **kwargs + ) + + args = ( + objects.ApplicationSpecificInformation(), + "application_namespace", + [] + ) + self.assertRaisesRegex( + TypeError, + "The application namespace must be a string.", + setattr, + *args + ) + + def test_invalid_application_data(self): + """ + Test that a TypeError is raised when an invalid application data value + is used to construct an ApplicationSpecificInformation attribute. + """ + kwargs = {"application_data": []} + self.assertRaisesRegex( + TypeError, + "The application data must be a string.", + objects.ApplicationSpecificInformation, + **kwargs + ) + + args = ( + objects.ApplicationSpecificInformation(), + "application_data", + [] + ) + self.assertRaisesRegex( + TypeError, + "The application data must be a string.", + setattr, + *args + ) + + def test_repr(self): + """ + Test that repr can be applied to an ApplicationSpecificInformation + attribute. + """ + app_specific_info = objects.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" + ) + + args = [ + "application_namespace='{}'".format("ssl"), + "application_data='{}'".format("www.example.com") + ] + + expected = "ApplicationSpecificInformation({})".format(", ".join(args)) + observed = repr(app_specific_info) + + self.assertEqual(expected, observed) + + def test_str(self): + """ + Test that str can be applied to an ApplicationSpecificInformation + attribute. + """ + app_specific_info = objects.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" + ) + + expected = str( + { + "application_namespace": "ssl", + "application_data": "www.example.com" + } + ) + observed = str(app_specific_info) + + self.assertEqual(expected, observed) + + def test_comparison_on_equal(self): + """ + Test that the equality/inequality operators return True/False when + comparing two ApplicationSpecificInformation attributes with the same + data. + """ + a = objects.ApplicationSpecificInformation() + b = objects.ApplicationSpecificInformation() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = objects.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" + ) + b = objects.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_application_namespaces(self): + """ + Test that the equality/inequality operators return False/True when + comparing two ApplicationSpecificInformation attributes with different + application namespaces. + """ + a = objects.ApplicationSpecificInformation( + application_namespace="a" + ) + b = objects.ApplicationSpecificInformation( + application_namespace="b" + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_application_data(self): + """ + Test that the equality/inequality operators return False/True when + comparing two ApplicationSpecificInformation attributes with different + application data. + """ + a = objects.ApplicationSpecificInformation( + application_data="a" + ) + b = objects.ApplicationSpecificInformation( + application_data="b" + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparing an ApplicationSpecificInformation attribute to a non + ApplicationSpecificInformation attribute. + """ + a = objects.ApplicationSpecificInformation() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_save(self): + """ + Test that an ApplicationSpecificInformation attribute can be saved + using SQLAlchemy. This test will add an attribute instance to the + database, verify that no exceptions are thrown, and check that its + ID was set. + """ + app_specific_info = objects.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" + ) + + engine = sqlalchemy.create_engine("sqlite:///:memory:", echo=True) + sqltypes.Base.metadata.create_all(engine) + + session = sqlalchemy.orm.sessionmaker(bind=engine)() + session.add(app_specific_info) + session.commit() + + self.assertIsNotNone(app_specific_info.id) + + def test_get(self): + """ + Test that an ApplicationSpecificInformation attribute can be saved + and then retrieved using SQLAlchemy. This test adds the attribute to + the database and then retrieves it by ID and verifies its values. + """ + app_specific_info = objects.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" + ) + + engine = sqlalchemy.create_engine("sqlite:///:memory:", echo=True) + sqltypes.Base.metadata.create_all(engine) + + session = sqlalchemy.orm.sessionmaker(bind=engine)() + session.add(app_specific_info) + session.commit() + + # Grab the ID now before making a new session to avoid a Detached error + # See http://sqlalche.me/e/bhk3 for more info. + app_specific_info_id = app_specific_info.id + + session = sqlalchemy.orm.sessionmaker(bind=engine)() + retrieved_info = session.query( + objects.ApplicationSpecificInformation + ).filter( + objects.ApplicationSpecificInformation.id == app_specific_info_id + ).one() + session.commit() + + self.assertEqual("ssl", retrieved_info.application_namespace) + self.assertEqual("www.example.com", retrieved_info.application_data) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/pie/objects/test_object_group.py python-pykmip-0.10.0/kmip/tests/unit/pie/objects/test_object_group.py --- python-pykmip-0.9.1/kmip/tests/unit/pie/objects/test_object_group.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/pie/objects/test_object_group.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,187 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sqlalchemy +import testtools + +from kmip.pie import objects +from kmip.pie import sqltypes + + +class TestObjectGroup(testtools.TestCase): + """ + Test suite for ObjectGroup. + """ + + def setUp(self): + super(TestObjectGroup, self).setUp() + + def tearDown(self): + super(TestObjectGroup, self).tearDown() + + def test_init(self): + """ + Test that an ObjectGroup object can be instantiated. + """ + object_group = objects.ObjectGroup() + + self.assertIsNone(object_group.object_group) + + def test_invalid_object_group(self): + """ + Test that a TypeError is raised when an invalid object group value + is used to construct an ObjectGroup attribute. + """ + kwargs = {"object_group": []} + self.assertRaisesRegex( + TypeError, + "The object group must be a string.", + objects.ObjectGroup, + **kwargs + ) + + args = ( + objects.ObjectGroup(), + "object_group", + [] + ) + self.assertRaisesRegex( + TypeError, + "The object group must be a string.", + setattr, + *args + ) + + def test_repr(self): + """ + Test that repr can be applied to an ObjectGroup attribute. + """ + object_group = objects.ObjectGroup(object_group="Group1") + + expected = "ObjectGroup({})".format( + "object_group='{}'".format("Group1") + ) + observed = repr(object_group) + + self.assertEqual(expected, observed) + + def test_str(self): + """ + Test that str can be applied to an ObjectGroup attribute. + """ + object_group = objects.ObjectGroup(object_group="Group1") + + expected = str( + { + "object_group": "Group1" + } + ) + observed = str(object_group) + + self.assertEqual(expected, observed) + + def test_comparison_on_equal(self): + """ + Test that the equality/inequality operators return True/False when + comparing two ObjectGroup attributes with the same + data. + """ + a = objects.ObjectGroup() + b = objects.ObjectGroup() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = objects.ObjectGroup(object_group="Group1") + b = objects.ObjectGroup(object_group="Group1") + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_object_groups(self): + """ + Test that the equality/inequality operators return False/True when + comparing two ObjectGroup attributes with different object groups. + """ + a = objects.ObjectGroup(object_group="a") + b = objects.ObjectGroup(object_group="b") + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparing an ObjectGroup attribute to a non ObjectGroup attribute. + """ + a = objects.ObjectGroup() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_save(self): + """ + Test that an ObjectGroup attribute can be saved using SQLAlchemy. This + test will add an attribute instance to the database, verify that no + exceptions are thrown, and check that its ID was set. + """ + object_group = objects.ObjectGroup(object_group="Group1") + + engine = sqlalchemy.create_engine("sqlite:///:memory:", echo=True) + sqltypes.Base.metadata.create_all(engine) + + session = sqlalchemy.orm.sessionmaker(bind=engine)() + session.add(object_group) + session.commit() + + self.assertIsNotNone(object_group.id) + + def test_get(self): + """ + Test that an ObjectGroup attribute can be saved and then retrieved + using SQLAlchemy. This test adds the attribute to the database and then + retrieves it by ID and verifies its values. + """ + object_group = objects.ObjectGroup(object_group="Group1") + + engine = sqlalchemy.create_engine("sqlite:///:memory:", echo=True) + sqltypes.Base.metadata.create_all(engine) + + session = sqlalchemy.orm.sessionmaker(bind=engine)() + session.add(object_group) + session.commit() + + # Grab the ID now before making a new session to avoid a Detached error + # See http://sqlalche.me/e/bhk3 for more info. + object_group_id = object_group.id + + session = sqlalchemy.orm.sessionmaker(bind=engine)() + retrieved_group = session.query( + objects.ObjectGroup + ).filter( + objects.ObjectGroup.id == object_group_id + ).one() + session.commit() + + self.assertEqual("Group1", retrieved_group.object_group) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/pie/objects/test_split_key.py python-pykmip-0.10.0/kmip/tests/unit/pie/objects/test_split_key.py --- python-pykmip-0.9.1/kmip/tests/unit/pie/objects/test_split_key.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/pie/objects/test_split_key.py 2020-02-25 16:05:27.000000000 +0000 @@ -0,0 +1,637 @@ +# Copyright (c) 2019 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import binascii +import testtools +import sqlalchemy + +from kmip.core import enums + +from kmip.pie import objects +from kmip.pie import sqltypes + + +class TestSplitKey(testtools.TestCase): + """ + Test suite for SplitKey. + """ + + def setUp(self): + super(TestSplitKey, self).setUp() + + self.engine = sqlalchemy.create_engine("sqlite:///:memory:", echo=True) + sqltypes.Base.metadata.create_all(self.engine) + + def tearDown(self): + super(TestSplitKey, self).tearDown() + + def test_init(self): + """ + Test that a SplitKey object can be instantiated. + """ + split_key = objects.SplitKey() + + self.assertIsNone(split_key.cryptographic_algorithm) + self.assertIsNone(split_key.cryptographic_length) + self.assertIsNone(split_key.value) + self.assertEqual(split_key.key_format_type, enums.KeyFormatType.RAW) + self.assertEqual(split_key.cryptographic_usage_masks, []) + self.assertEqual(split_key.names, ["Split Key"]) + self.assertIsNone(split_key.split_key_parts) + self.assertIsNone(split_key.key_part_identifier) + self.assertIsNone(split_key.split_key_threshold) + self.assertIsNone(split_key.split_key_method) + self.assertIsNone(split_key.prime_field_size) + + def test_init_with_args(self): + """ + Test that a SplitKey object can be instantiated with all arguments. + """ + split_key = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ), + key_format_type=enums.KeyFormatType.RAW, + cryptographic_usage_masks=[ + enums.CryptographicUsageMask.EXPORT + ], + name="Test Split Key", + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729 + ) + + self.assertEqual( + split_key.cryptographic_algorithm, + enums.CryptographicAlgorithm.AES + ) + self.assertEqual(split_key.cryptographic_length, 128) + self.assertEqual( + split_key.value, + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ) + self.assertEqual(split_key.key_format_type, enums.KeyFormatType.RAW) + self.assertEqual( + split_key.cryptographic_usage_masks, + [enums.CryptographicUsageMask.EXPORT] + ) + self.assertEqual(split_key.names, ["Test Split Key"]) + self.assertEqual(split_key.split_key_parts, 4) + self.assertEqual(split_key.key_part_identifier, 1) + self.assertEqual(split_key.split_key_threshold, 2) + self.assertEqual( + split_key.split_key_method, + enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8 + ) + self.assertEqual(split_key.prime_field_size, 104729) + + def test_invalid_split_key_parts(self): + """ + Test that a TypeError is raised when an invalid split key parts value + is used to construct a SplitKey. + """ + kwargs = {"split_key_parts": "invalid"} + + self.assertRaisesRegex( + TypeError, + "The split key parts must be an integer.", + objects.SplitKey, + **kwargs + ) + + args = ( + objects.SplitKey(), + "split_key_parts", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The split key parts must be an integer.", + setattr, + *args + ) + + def test_invalid_key_part_identifier(self): + """ + Test that a TypeError is raised when an invalid key part identifier + value is used to construct a SplitKey. + """ + kwargs = {"key_part_identifier": "invalid"} + + self.assertRaisesRegex( + TypeError, + "The key part identifier must be an integer.", + objects.SplitKey, + **kwargs + ) + + args = ( + objects.SplitKey(), + "key_part_identifier", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The key part identifier must be an integer.", + setattr, + *args + ) + + def test_invalid_split_key_threshold(self): + """ + Test that a TypeError is raised when an invalid split key threshold + value is used to construct a SplitKey. + """ + kwargs = {"split_key_threshold": "invalid"} + + self.assertRaisesRegex( + TypeError, + "The split key threshold must be an integer.", + objects.SplitKey, + **kwargs + ) + + args = ( + objects.SplitKey(), + "split_key_threshold", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The split key threshold must be an integer.", + setattr, + *args + ) + + def test_invalid_split_key_method(self): + """ + Test that a TypeError is raised when an invalid split key method value + is used to construct a SplitKey. + """ + kwargs = {"split_key_method": "invalid"} + + self.assertRaisesRegex( + TypeError, + "The split key method must be a SplitKeyMethod enumeration.", + objects.SplitKey, + **kwargs + ) + + args = ( + objects.SplitKey(), + "split_key_method", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The split key method must be a SplitKeyMethod enumeration.", + setattr, + *args + ) + + def test_invalid_prime_field_size(self): + """ + Test that a TypeError is raised when an invalid prime field size value + is used to construct a SplitKey. + """ + kwargs = {"prime_field_size": "invalid"} + + self.assertRaisesRegex( + TypeError, + "The prime field size must be an integer.", + objects.SplitKey, + **kwargs + ) + + args = ( + objects.SplitKey(), + "prime_field_size", + "invalid" + ) + self.assertRaisesRegex( + TypeError, + "The prime field size must be an integer.", + setattr, + *args + ) + + def test_repr(self): + """ + Test that repr can be applied to a SplitKey. + """ + split_key = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ), + key_format_type=enums.KeyFormatType.RAW, + cryptographic_usage_masks=[ + enums.CryptographicUsageMask.EXPORT + ], + name="Test Split Key", + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729 + ) + + args = [ + "cryptographic_algorithm={}".format( + enums.CryptographicAlgorithm.AES + ), + "cryptographic_length={}".format(128), + "key_value={}".format( + binascii.hexlify( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ) + ), + "key_format_type={}".format(enums.KeyFormatType.RAW), + "key_wrapping_data={}".format({}), + "cryptographic_usage_masks={}".format( + [enums.CryptographicUsageMask.EXPORT] + ), + "name={}".format(["Test Split Key"]), + "split_key_parts=4", + "key_part_identifier=1", + "split_key_threshold=2", + "split_key_method={}".format( + enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8 + ), + "prime_field_size=104729" + ] + + expected = "SplitKey({})".format(", ".join(args)) + observed = repr(split_key) + + self.assertEqual(expected, observed) + + def test_str(self): + """ + Test that str can be applied to a SplitKey. + """ + split_key = objects.SplitKey( + key_value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ) + ) + + expected = str(binascii.hexlify(split_key.value)) + observed = str(split_key) + + self.assertEqual(expected, observed) + + def test_comparison_on_equal(self): + """ + Test that the equality/inequality operators return True/False when + comparing two SplitKey objects with the same data. + """ + a = objects.SplitKey() + b = objects.SplitKey() + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + a = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ), + key_format_type=enums.KeyFormatType.RAW, + cryptographic_usage_masks=[ + enums.CryptographicUsageMask.EXPORT + ], + name="Test Split Key", + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729 + ) + b = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ), + key_format_type=enums.KeyFormatType.RAW, + cryptographic_usage_masks=[ + enums.CryptographicUsageMask.EXPORT + ], + name="Test Split Key", + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729 + ) + + self.assertTrue(a == b) + self.assertTrue(b == a) + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_comparison_on_different_cryptographic_algorithms(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different cryptographic algorithms. + """ + a = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES + ) + b = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.RSA + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_cryptographic_lengths(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different cryptographic lengths. + """ + a = objects.SplitKey(cryptographic_length=128) + b = objects.SplitKey(cryptographic_length=256) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_values(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different values. + """ + a = objects.SplitKey(key_value=b'\x00') + b = objects.SplitKey(key_value=b'\xFF') + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_key_format_types(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different key format types. + """ + a = objects.SplitKey(key_format_type=enums.KeyFormatType.RAW) + b = objects.SplitKey(key_format_type=enums.KeyFormatType.OPAQUE) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_key_wrapping_data(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different key wrapping data. + """ + a = objects.SplitKey(key_wrapping_data={}) + b = objects.SplitKey( + key_wrapping_data={"wrapping_method": enums.WrappingMethod.ENCRYPT} + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_cryptographic_usage_masks(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different cryptographic usage + masks. + """ + a = objects.SplitKey( + cryptographic_usage_masks=[enums.CryptographicUsageMask.ENCRYPT] + ) + b = objects.SplitKey( + cryptographic_usage_masks=[enums.CryptographicUsageMask.EXPORT] + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_names(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different names. + """ + a = objects.SplitKey(name="Test Split Key") + b = objects.SplitKey(name="Split Key Test") + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_split_key_parts(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different split key parts. + """ + a = objects.SplitKey(split_key_parts=4) + b = objects.SplitKey(split_key_parts=5) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_key_part_identifiers(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different key part identifiers. + """ + a = objects.SplitKey(key_part_identifier=1) + b = objects.SplitKey(key_part_identifier=2) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_split_key_thresholds(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different split key thresholds. + """ + a = objects.SplitKey(split_key_threshold=1) + b = objects.SplitKey(split_key_threshold=2) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_split_key_methods(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different split key methods. + """ + a = objects.SplitKey(split_key_method=enums.SplitKeyMethod.XOR) + b = objects.SplitKey( + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8 + ) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_different_prime_field_sizes(self): + """ + Test that the equality/inequality operators return False/True when + comparing two SplitKey objects with different prime field sizes. + """ + a = objects.SplitKey(prime_field_size=13) + b = objects.SplitKey(prime_field_size=104729) + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_comparison_on_type_mismatch(self): + """ + Test that the equality/inequality operators return False/True when + comparing a SplitKey object to a non-SplitKey object. + """ + a = objects.SplitKey() + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_save(self): + """ + Test that a SplitKey object can be saved using SQLAlchemy. This will + add it to the database, verify that no exceptions are thrown, and check + that its unique identifier was set. + """ + split_key = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ), + key_format_type=enums.KeyFormatType.RAW, + cryptographic_usage_masks=[ + enums.CryptographicUsageMask.EXPORT + ], + name="Test Split Key", + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729 + ) + + session = sqlalchemy.orm.sessionmaker(bind=self.engine)() + session.add(split_key) + session.commit() + + self.assertIsNotNone(split_key.unique_identifier) + + def test_get(self): + """ + Test that a SplitKey object can be saved and then retrieved using + SQLAlchemy. This test adds the object to the database and then + retrieves it by ID and verifies some of the attributes. + """ + split_key = objects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ), + key_format_type=enums.KeyFormatType.RAW, + cryptographic_usage_masks=[ + enums.CryptographicUsageMask.EXPORT + ], + name="Test Split Key", + split_key_parts=4, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8, + prime_field_size=104729 + ) + + session = sqlalchemy.orm.sessionmaker(bind=self.engine)() + session.add(split_key) + session.commit() + + session = sqlalchemy.orm.sessionmaker(bind=self.engine)() + retrieved_key = session.query(objects.SplitKey).filter( + objects.ManagedObject.unique_identifier == + split_key.unique_identifier + ).one() + session.commit() + + self.assertEqual(retrieved_key.names, ["Test Split Key"]) + self.assertEqual( + retrieved_key.cryptographic_algorithm, + enums.CryptographicAlgorithm.AES + ) + self.assertEqual(retrieved_key.cryptographic_length, 128) + self.assertEqual( + retrieved_key.value, + ( + b'\x66\xC4\x6A\x77\x54\xF9\x4D\xE4' + b'\x20\xC7\xB1\xA7\xFF\xF5\xEC\x56' + ) + ) + self.assertEqual( + retrieved_key.key_format_type, + enums.KeyFormatType.RAW + ) + self.assertEqual( + retrieved_key.cryptographic_usage_masks, + [enums.CryptographicUsageMask.EXPORT] + ) + self.assertEqual(retrieved_key.split_key_parts, 4) + self.assertEqual(retrieved_key.key_part_identifier, 1) + self.assertEqual(retrieved_key.split_key_threshold, 2) + self.assertEqual( + retrieved_key.split_key_method, + enums.SplitKeyMethod.POLYNOMIAL_SHARING_GF_2_8 + ) + self.assertEqual(retrieved_key.prime_field_size, 104729) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/pie/test_client.py python-pykmip-0.10.0/kmip/tests/unit/pie/test_client.py --- python-pykmip-0.9.1/kmip/tests/unit/pie/test_client.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/pie/test_client.py 2020-02-25 16:05:27.000000000 +0000 @@ -24,6 +24,8 @@ from kmip.core.factories import attributes from kmip.core.messages import contents +from kmip.core.messages import payloads +from kmip.core import primitives from kmip.core.primitives import DateTime from kmip.services.kmip_client import KMIPProxy @@ -573,34 +575,35 @@ specifically testing that the private / public names are correctly sent with the request """ - # Create the template to test the create key pair call - algorithm = enums.CryptographicAlgorithm.RSA - length = 2048 - algorithm_attribute = self.attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, algorithm) - length_attribute = self.attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, length) - - private_name_attribute = self.attribute_factory.create_attribute( - enums.AttributeType.NAME, "private") - public_name_attribute = self.attribute_factory.create_attribute( - enums.AttributeType.NAME, "public") - - pair_attributes = [ - algorithm_attribute, - length_attribute - ] - - template = obj.TemplateAttribute( - attributes=pair_attributes, + common_template = obj.TemplateAttribute( + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.RSA + ), + self.attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 2048 + ) + ], tag=enums.Tags.COMMON_TEMPLATE_ATTRIBUTE ) private_template = obj.TemplateAttribute( - names=[private_name_attribute], + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.NAME, + "Test_Private_Key" + ) + ], tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE ) public_template = obj.TemplateAttribute( - names=[public_name_attribute], + attributes=[ + self.attribute_factory.create_attribute( + enums.AttributeType.NAME, + "Test_Public_Key" + ) + ], tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE ) @@ -617,12 +620,12 @@ public_uid, private_uid = client.create_key_pair( enums.CryptographicAlgorithm.RSA, 2048, - public_name="public", - private_name="private" + public_name="Test_Public_Key", + private_name="Test_Private_Key" ) kwargs = { - "common_template_attribute": template, + "common_template_attribute": common_template, "private_key_template_attribute": private_template, "public_key_template_attribute": public_template } @@ -758,6 +761,120 @@ client.create_key_pair, *args) @mock.patch( + "kmip.pie.client.KMIPProxy", + mock.MagicMock(spec_set=KMIPProxy) + ) + def test_delete_attribute(self): + """ + Test that the client can delete an attribute. + """ + request_payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_name="Object Group", + attribute_index=2 + ) + response_payload = payloads.DeleteAttributeResponsePayload( + unique_identifier="1", + attribute=None + ) + + with ProxyKmipClient() as client: + client.proxy.send_request_payload.return_value = response_payload + + unique_identifier, attribute = client.delete_attribute( + "1", + attribute_name="Object Group", + attribute_index=2 + ) + + args = ( + enums.Operation.DELETE_ATTRIBUTE, + request_payload + ) + client.proxy.send_request_payload.assert_called_with(*args) + self.assertEqual("1", unique_identifier) + self.assertIsNone(attribute) + + @mock.patch( + "kmip.pie.client.KMIPProxy", + mock.MagicMock(spec_set=KMIPProxy) + ) + def test_set_attribute(self): + """ + Test that the client can set an attribute. + """ + request_payload = payloads.SetAttributeRequestPayload( + unique_identifier="1", + new_attribute=obj.NewAttribute( + attribute=primitives.Boolean( + value=True, + tag=enums.Tags.SENSITIVE + ) + ) + ) + response_payload = payloads.SetAttributeResponsePayload( + unique_identifier="1" + ) + + with ProxyKmipClient() as client: + client.proxy.send_request_payload.return_value = response_payload + + unique_identifier = client.set_attribute( + "1", + attribute_name="Sensitive", + attribute_value=True + ) + + args = ( + enums.Operation.SET_ATTRIBUTE, + request_payload + ) + client.proxy.send_request_payload.assert_called_with(*args) + self.assertEqual("1", unique_identifier) + + @mock.patch( + "kmip.pie.client.KMIPProxy", + mock.MagicMock(spec_set=KMIPProxy) + ) + def test_modify_attribute(self): + """ + Test that the client can modify an attribute. + """ + request_payload = payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + new_attribute=obj.NewAttribute( + attribute=primitives.Boolean( + value=True, + tag=enums.Tags.SENSITIVE + ) + ) + ) + response_payload = payloads.ModifyAttributeResponsePayload( + unique_identifier="1" + ) + + with ProxyKmipClient() as client: + client.proxy.send_request_payload.return_value = response_payload + + unique_identifier, attribute = client.modify_attribute( + unique_identifier="1", + new_attribute=obj.NewAttribute( + attribute=primitives.Boolean( + value=True, + tag=enums.Tags.SENSITIVE + ) + ) + ) + + args = ( + enums.Operation.MODIFY_ATTRIBUTE, + request_payload + ) + client.proxy.send_request_payload.assert_called_with(*args) + self.assertEqual("1", unique_identifier) + self.assertIsNone(attribute) + + @mock.patch( 'kmip.pie.client.KMIPProxy', mock.MagicMock(spec_set=KMIPProxy) ) def test_rekey(self): diff -Nru python-pykmip-0.9.1/kmip/tests/unit/pie/test_factory.py python-pykmip-0.10.0/kmip/tests/unit/pie/test_factory.py --- python-pykmip-0.9.1/kmip/tests/unit/pie/test_factory.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/pie/test_factory.py 2020-02-25 16:05:27.000000000 +0000 @@ -430,6 +430,88 @@ self.assertEqual(enums.OpaqueDataType.NONE, pie_obj.opaque_type) self.assertEqual(self.opaque_bytes, pie_obj.value) + def test_convert_split_key_pie_to_core(self): + """ + Test that a Pie split key object can be converted into a core split + key object. + """ + pie_split_key = pobjects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=self.symmetric_bytes, + cryptographic_usage_masks=[enums.CryptographicUsageMask.EXPORT], + name="Split Key", + key_format_type=enums.KeyFormatType.RAW, + key_wrapping_data=None, + split_key_parts=3, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.XOR, + prime_field_size=None + ) + core_split_key = self.factory.convert(pie_split_key) + + self.assertIsInstance(core_split_key, secrets.SplitKey) + self._test_core_key( + core_split_key, + enums.CryptographicAlgorithm.AES, + 128, + self.symmetric_bytes, + enums.KeyFormatType.RAW + ) + self.assertEqual(3, core_split_key.split_key_parts) + self.assertEqual(1, core_split_key.key_part_identifier) + self.assertEqual(2, core_split_key.split_key_threshold) + self.assertEqual( + enums.SplitKeyMethod.XOR, + core_split_key.split_key_method + ) + self.assertIsNone(core_split_key.prime_field_size) + + def test_convert_split_key_core_to_pie(self): + """ + Test that a core split key object can be converted into a Pie split + key object. + """ + key_block = cobjects.KeyBlock( + key_format_type=misc.KeyFormatType(enums.KeyFormatType.RAW), + key_compression_type=None, + key_value=cobjects.KeyValue( + cobjects.KeyMaterial(self.symmetric_bytes) + ), + cryptographic_algorithm=attributes.CryptographicAlgorithm( + enums.CryptographicAlgorithm.AES + ), + cryptographic_length=attributes.CryptographicLength(128), + key_wrapping_data=None + ) + core_split_key = secrets.SplitKey( + split_key_parts=3, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.XOR, + prime_field_size=None, + key_block=key_block + ) + pie_split_key = self.factory.convert(core_split_key) + + self.assertIsInstance(pie_split_key, pobjects.SplitKey) + self._test_pie_key( + pie_split_key, + enums.CryptographicAlgorithm.AES, + 128, + self.symmetric_bytes, + enums.KeyFormatType.RAW + ) + self.assertEqual(3, pie_split_key.split_key_parts) + self.assertEqual(1, pie_split_key.key_part_identifier) + self.assertEqual(2, pie_split_key.split_key_threshold) + self.assertEqual( + enums.SplitKeyMethod.XOR, + pie_split_key.split_key_method + ) + self.assertIsNone(pie_split_key.prime_field_size) + def test_build_pie_symmetric_key(self): """ Test that a core SymmetricKey object can be converted into a Pie @@ -531,6 +613,88 @@ self.assertRaises( TypeError, self.factory._build_pie_certificate, *args) + def test_build_core_split_key(self): + """ + Test that a Pie split key object can be converted into a core key + object. + """ + pie_split_key = pobjects.SplitKey( + cryptographic_algorithm=enums.CryptographicAlgorithm.AES, + cryptographic_length=128, + key_value=self.symmetric_bytes, + cryptographic_usage_masks=[enums.CryptographicUsageMask.EXPORT], + name="Split Key", + key_format_type=enums.KeyFormatType.RAW, + key_wrapping_data=None, + split_key_parts=3, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.XOR, + prime_field_size=None + ) + core_split_key = self.factory._build_core_split_key(pie_split_key) + + self.assertIsInstance(core_split_key, secrets.SplitKey) + self._test_core_key( + core_split_key, + enums.CryptographicAlgorithm.AES, + 128, + self.symmetric_bytes, + enums.KeyFormatType.RAW + ) + self.assertEqual(3, core_split_key.split_key_parts) + self.assertEqual(1, core_split_key.key_part_identifier) + self.assertEqual(2, core_split_key.split_key_threshold) + self.assertEqual( + enums.SplitKeyMethod.XOR, + core_split_key.split_key_method + ) + self.assertIsNone(core_split_key.prime_field_size) + + def test_build_pie_split_key(self): + """ + Test that a core split key object can be converted into a Pie split + key object. + """ + key_block = cobjects.KeyBlock( + key_format_type=misc.KeyFormatType(enums.KeyFormatType.RAW), + key_compression_type=None, + key_value=cobjects.KeyValue( + cobjects.KeyMaterial(self.symmetric_bytes) + ), + cryptographic_algorithm=attributes.CryptographicAlgorithm( + enums.CryptographicAlgorithm.AES + ), + cryptographic_length=attributes.CryptographicLength(128), + key_wrapping_data=None + ) + core_split_key = secrets.SplitKey( + split_key_parts=3, + key_part_identifier=1, + split_key_threshold=2, + split_key_method=enums.SplitKeyMethod.XOR, + prime_field_size=None, + key_block=key_block + ) + pie_split_key = self.factory._build_pie_split_key(core_split_key) + + self.assertIsInstance(pie_split_key, pobjects.SplitKey) + self._test_pie_key( + pie_split_key, + enums.CryptographicAlgorithm.AES, + 128, + self.symmetric_bytes, + enums.KeyFormatType.RAW + ) + self.assertEqual(3, pie_split_key.split_key_parts) + self.assertEqual(1, pie_split_key.key_part_identifier) + self.assertEqual(2, pie_split_key.split_key_threshold) + self.assertEqual( + enums.SplitKeyMethod.XOR, + pie_split_key.split_key_method + ) + self.assertIsNone(pie_split_key.prime_field_size) + def _test_core_key(self, key, algorithm, length, value, format_type): key_block = key.key_block self.assertIsInstance(key_block, cobjects.KeyBlock) diff -Nru python-pykmip-0.9.1/kmip/tests/unit/services/server/crypto/test_engine.py python-pykmip-0.10.0/kmip/tests/unit/services/server/crypto/test_engine.py --- python-pykmip-0.9.1/kmip/tests/unit/services/server/crypto/test_engine.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/services/server/crypto/test_engine.py 2020-02-25 16:05:27.000000000 +0000 @@ -1261,6 +1261,9 @@ # TODO(peter-hamilton): Replace this with actual fixture files from NIST CAPV. # Most of these test vectors were obtained from the pyca/cryptography test # suite. +# GCM test vectors were obtained from the NIST CAVP test suite: +# +# https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/mac/gcmtestvectors.zip @pytest.fixture( scope='function', params=[ @@ -1331,6 +1334,87 @@ b'\x6a\xcc\x04\x14\x2e\x10\x0a\x65' b'\xf5\x1b\x97\xad\xf5\x17\x2c\x41' )}, + {'algorithm': enums.CryptographicAlgorithm.AES, + 'cipher_mode': enums.BlockCipherMode.GCM, + 'key': ( + b'\xfe\x32\xb0\xc7\x4c\xd7\x45\x8b' + b'\x75\xcb\x19\x6f\x48\x6b\x35\xc6' + b'\x19\xb7\xc6\xb4\xfe\x3f\x49\x64' + b'\xa4\x9a\xd9\x25\x37\x76\x27\xd7' + ), + 'iv_nonce': ( + b'\x66\x30\xe6\xd4\xb9\xd9\x04\x1f' + b'\xe2\xba\xf6\xd1\xd6\x88\x7a\x56' + b'\x4e\xfe\xe7\x54\x90\xc2\xdd\x6f' + b'\x5d\x3e\x7f\xb4\xc3\xac\x4d\xe9' + b'\xfd\xa1\x69\x74\x71\xcc\x14\x80' + b'\x3a\x03\x3f\x55\x1d\x2e\x05\x56' + b'\x19\xd9\xb6\x84\x83\x08\xb9\xf2' + b'\x53\x5b\x0d\x85\x43\x8f\x16\x02' + b'\x3c\x1b\x96\x81\xb2\x62\xa5\xf3' + b'\xd5\x43\x95\xec\xd9\x56\x3b\x88' + b'\x10\x8b\xe8\xad\x4a\x78\xee\x2a' + b'\x4d\xec\xe8\x88\xc4\xc3\x4c\xda' + b'\xe6\xaf\x21\xd8\xef\xc5\xcf\x71' + b'\x9e\xfa\x27\x04\x9b\x4a\x45\xcc' + b'\x49\x70\xdb\xba\x37\xef\x57\x15' + b'\xa9\x9a\x96\x44\xae\xd0\xd3\x94' + ), + 'plain_text': ( + b'\x40\x31\x55\x40\x39\x07\x4e\x10' + b'\x5d\xb2\x36\xdd\x8b\x7c\x81\xb6' + b'\x7e\xc1\xd7\xa4\xed\x0d\xd5\x94' + b'\x8e\x85\xa0\x0f\x3f\x6d\x4c\x87' + b'\x2d\xc8\x72\xc8\x7b\x47\xc4\x5a' + b'\xf1\x81\xf0\x39\x58\xc1\xee\xfe' + b'\x60\x62\xff' + ), + 'auth_additional_data': ( + b'\xd3\xc6\x2d\xa2\x77\x97\xba\x8e\x16' + b'\x82\x1a\x1b\xe2\x47\x8a\x6f' + ), + 'auth_tag_length': 16, + 'cipher_text': ( + b'\xfb\x10\xfa\x35\x45\x92\x53\xab' + b'\x7a\x87\xb3\x27\x32\x63\x56\x05' + b'\x56\xb8\x49\xba\x6b\xf1\xf5\xde' + b'\x46\xd4\xc8\x59\xf8\xad\xa6\xca' + b'\xca\xe4\x53\x9a\x5b\x7e\xaf\x9a' + b'\xd1\x16\xd4\x56\xf5\x0d\x2f\x80' + b'\xb6\x3d\xd7' + ), + 'auth_tag': ( + b'\xbd\x9b\x6f\x23\xc9\x39\xa7\xd4' + b'\xf5\xbe\xb0\x9d\x92\xf0\x17\x56' + )}, + {'algorithm': enums.CryptographicAlgorithm.AES, + 'cipher_mode': enums.BlockCipherMode.GCM, + 'key': ( + b'\x2c\xd6\xfd\x85\xf1\x30\x28\x38' + b'\x63\x53\xff\xa1\x52\x1d\x8d\x7b' + b'\xc8\xeb\xed\x26\xb1\x6d\x94\x40' + b'\x5f\x03\xf6\xda\x5d\xef\x2d\xa8' + ), + 'iv_nonce': ( + b'\xba\x7a\x97\x67\x0f\xbb\x02\x62' + b'\x24\x36\x92\x9d' + ), + 'plain_text': ( + b'\x8b\x4f\x7e\x75\x16\x31\xe7\x65' + b'\xdc\x13\xfa\x63\xf0\x2f\x63\x4b' + ), + 'auth_additional_data': ( + b'\x90\xee\x7e\x56\xf9\x59\x34\x76' + b'\x1c\x39\xab\x75\x37\x2a\xc2\xc6' + ), + 'auth_tag_length': 8, + 'cipher_text': ( + b'\x8c\xdc\x3f\x57\x48\xb1\x59\x36' + b'\x6c\x94\xaf\x48\xe2\xcf\xa0\x98' + ), + 'auth_tag': ( + b'\xfe\xb3\x8e\x85\x4e\xdf\x4d\x79' + )}, {'algorithm': enums.CryptographicAlgorithm.BLOWFISH, 'cipher_mode': enums.BlockCipherMode.OFB, 'key': ( @@ -1441,7 +1525,9 @@ symmetric_parameters.get('key'), symmetric_parameters.get('plain_text'), cipher_mode=symmetric_parameters.get('cipher_mode'), - iv_nonce=symmetric_parameters.get('iv_nonce') + iv_nonce=symmetric_parameters.get('iv_nonce'), + auth_additional_data=symmetric_parameters.get('auth_additional_data'), + auth_tag_length=symmetric_parameters.get('auth_tag_length') ) if engine._handle_symmetric_padding.called: @@ -1454,6 +1540,7 @@ ) assert symmetric_parameters.get('cipher_text') == result.get('cipher_text') + assert symmetric_parameters.get('auth_tag') == result.get('auth_tag') def test_decrypt_symmetric(symmetric_parameters): @@ -1472,7 +1559,9 @@ symmetric_parameters.get('key'), symmetric_parameters.get('cipher_text'), cipher_mode=symmetric_parameters.get('cipher_mode'), - iv_nonce=symmetric_parameters.get('iv_nonce') + iv_nonce=symmetric_parameters.get('iv_nonce'), + auth_additional_data=symmetric_parameters.get('auth_additional_data'), + auth_tag=symmetric_parameters.get('auth_tag') ) if engine._handle_symmetric_padding.called: diff -Nru python-pykmip-0.9.1/kmip/tests/unit/services/server/test_engine.py python-pykmip-0.10.0/kmip/tests/unit/services/server/test_engine.py --- python-pykmip-0.9.1/kmip/tests/unit/services/server/test_engine.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/services/server/test_engine.py 2020-02-25 16:05:27.000000000 +0000 @@ -702,8 +702,11 @@ e = engine.KmipEngine() e._logger = mock.MagicMock() + # TODO (peterhamilton) Alphatize these. e._process_create = mock.MagicMock() e._process_create_key_pair = mock.MagicMock() + e._process_delete_attribute = mock.MagicMock() + e._process_derive_key = mock.MagicMock() e._process_register = mock.MagicMock() e._process_locate = mock.MagicMock() e._process_get = mock.MagicMock() @@ -719,9 +722,13 @@ e._process_signature_verify = mock.MagicMock() e._process_mac = mock.MagicMock() e._process_sign = mock.MagicMock() + e._process_set_attribute = mock.MagicMock() + e._process_modify_attribute = mock.MagicMock() e._process_operation(enums.Operation.CREATE, None) e._process_operation(enums.Operation.CREATE_KEY_PAIR, None) + e._process_operation(enums.Operation.DELETE_ATTRIBUTE, None) + e._process_operation(enums.Operation.DERIVE_KEY, None) e._process_operation(enums.Operation.REGISTER, None) e._process_operation(enums.Operation.LOCATE, None) e._process_operation(enums.Operation.GET, None) @@ -737,9 +744,13 @@ e._process_operation(enums.Operation.SIGN, None) e._process_operation(enums.Operation.SIGNATURE_VERIFY, None) e._process_operation(enums.Operation.MAC, None) + e._process_operation(enums.Operation.SET_ATTRIBUTE, None) + e._process_operation(enums.Operation.MODIFY_ATTRIBUTE, None) e._process_create.assert_called_with(None) e._process_create_key_pair.assert_called_with(None) + e._process_delete_attribute.assert_called_with(None) + e._process_derive_key.assert_called_with(None) e._process_register.assert_called_with(None) e._process_locate.assert_called_with(None) e._process_get.assert_called_with(None) @@ -754,6 +765,8 @@ e._process_decrypt.assert_called_with(None) e._process_signature_verify.assert_called_with(None) e._process_mac.assert_called_with(None) + e._process_set_attribute.assert_called_with(None) + e._process_modify_attribute.assert_called_with(None) def test_unsupported_operation(self): """ @@ -1038,10 +1051,10 @@ class DummyObject: def __init__(self): - self._object_type = enums.ObjectType.SPLIT_KEY + self._object_type = enums.ObjectType.TEMPLATE args = (DummyObject(), ) - regex = "The SplitKey object type is not supported." + regex = "The Template object type is not supported." six.assertRaisesRegex( self, exceptions.InvalidField, @@ -1584,7 +1597,7 @@ symmetric_key, 'Object Group' ) - self.assertEqual(None, result) + self.assertEqual([], result) result = e._get_attribute_from_managed_object( symmetric_key, @@ -1602,7 +1615,7 @@ symmetric_key, 'Application Specific Information' ) - self.assertEqual(None, result) + self.assertEqual([], result) result = e._get_attribute_from_managed_object( symmetric_key, @@ -1622,6 +1635,344 @@ ) self.assertEqual(None, result) + def test_get_attribute_index_from_managed_object(self): + """ + Test that an attribute's index can be retrieved from a given managed + object. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() + + symmetric_key = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'', + masks=[enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.DECRYPT] + ) + certificate = pie_objects.X509Certificate( + b'' + ) + + e._data_session.add(symmetric_key) + e._data_session.add(certificate) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._set_attribute_on_managed_object( + symmetric_key, + ( + "Application Specific Information", + [ + attributes.ApplicationSpecificInformation( + application_namespace="Example Namespace", + application_data="Example Data" + ) + ] + ) + ) + e._set_attribute_on_managed_object( + symmetric_key, + ( + "Name", + [ + attributes.Name( + name_value=attributes.Name.NameValue("Name 1") + ), + attributes.Name( + name_value=attributes.Name.NameValue("Name 2") + ) + ] + ) + ) + e._set_attribute_on_managed_object( + symmetric_key, + ( + "Object Group", + [ + primitives.TextString( + "Example Group", + tag=enums.Tags.OBJECT_GROUP + ) + ] + ) + ) + + # Test getting the index for an ApplicationSpecificInfo attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Application Specific Information", + attributes.ApplicationSpecificInformation( + application_namespace="Example Namespace", + application_data="Example Data" + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Application Specific Information", + attributes.ApplicationSpecificInformation( + application_namespace="Wrong Namespace", + application_data="Wrong Data" + ) + ) + self.assertIsNone(index) + + # Test getting the index for a CertificateType attribute + index = e._get_attribute_index_from_managed_object( + certificate, + "Certificate Type", + primitives.Enumeration( + enums.CertificateType, + enums.CertificateType.X_509, + tag=enums.Tags.CERTIFICATE_TYPE + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + certificate, + "Certificate Type", + primitives.Enumeration( + enums.CertificateType, + enums.CertificateType.PGP, + tag=enums.Tags.CERTIFICATE_TYPE + ) + ) + self.assertIsNone(index) + + # Test getting the index for a CryptographicAlgorithm attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Cryptographic Algorithm", + primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.AES, + tag=enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Cryptographic Algorithm", + primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.RSA, + tag=enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + self.assertIsNone(index) + + # Test getting the index for a CryptographicLength attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Cryptographic Length", + primitives.Integer( + 0, + tag=enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Cryptographic Length", + primitives.Integer( + 128, + tag=enums.Tags.CRYPTOGRAPHIC_LENGTH + ) + ) + self.assertIsNone(index) + + # Test getting the index for a CryptographicUsageMasks attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Cryptographic Usage Mask", + primitives.Integer( + 12, + tag=enums.Tags.CRYPTOGRAPHIC_USAGE_MASK + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Cryptographic Usage Mask", + primitives.Integer( + 0, + tag=enums.Tags.CRYPTOGRAPHIC_USAGE_MASK + ) + ) + self.assertIsNone(index) + + # Test getting the index for a InitialDate attribute + date = e._get_attribute_from_managed_object( + symmetric_key, + "Initial Date" + ) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Initial Date", + primitives.DateTime( + date, + tag=enums.Tags.INITIAL_DATE + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Initial Date", + primitives.DateTime( + 9999, + tag=enums.Tags.INITIAL_DATE + ) + ) + self.assertIsNone(index) + + # Test getting the index for a Name attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Name", + attributes.Name( + name_value=attributes.Name.NameValue("Name 2") + ) + ) + self.assertEqual(2, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Name", + attributes.Name( + name_value=attributes.Name.NameValue("Name 3") + ) + ) + self.assertIsNone(index) + + # Test getting the index for a ObjectGroup attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Object Group", + primitives.TextString( + "Example Group", + tag=enums.Tags.OBJECT_GROUP + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Object Group", + primitives.TextString( + "Invalid Group", + tag=enums.Tags.OBJECT_GROUP + ) + ) + self.assertIsNone(index) + + # Test getting the index for a ObjectType attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Object Type", + primitives.Enumeration( + enums.ObjectType, + enums.ObjectType.SYMMETRIC_KEY, + tag=enums.Tags.OBJECT_TYPE + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Object Type", + primitives.Enumeration( + enums.ObjectType, + enums.ObjectType.CERTIFICATE, + tag=enums.Tags.OBJECT_TYPE + ) + ) + self.assertIsNone(index) + + # Test getting the index for a OperationPolicyName attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Operation Policy Name", + primitives.TextString( + "default", + tag=enums.Tags.OPERATION_POLICY_NAME + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Operation Policy Name", + primitives.TextString( + "invalid", + tag=enums.Tags.OPERATION_POLICY_NAME + ) + ) + self.assertIsNone(index) + + # Test getting the index for a Sensitive attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Sensitive", + primitives.Boolean( + False, + tag=enums.Tags.SENSITIVE + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Sensitive", + primitives.Boolean( + True, + tag=enums.Tags.SENSITIVE + ) + ) + self.assertIsNone(index) + + # Test getting the index for a State attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "State", + primitives.Enumeration( + enums.State, + enums.State.PRE_ACTIVE, + tag=enums.Tags.STATE + ) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "State", + primitives.Enumeration( + enums.State, + enums.State.ACTIVE, + tag=enums.Tags.STATE + ) + ) + self.assertIsNone(index) + + # Test getting the index for a UniqueIdentifier attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Unique Identifier", + primitives.TextString(value="1", tag=enums.Tags.UNIQUE_IDENTIFIER) + ) + self.assertEqual(0, index) + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Unique Identifier", + primitives.TextString(value="9", tag=enums.Tags.UNIQUE_IDENTIFIER) + ) + self.assertIsNone(index) + + # Test getting the index for an unsupported attribute + index = e._get_attribute_index_from_managed_object( + symmetric_key, + "Archive Date", + None + ) + self.assertIsNone(index) + def test_set_attributes_on_managed_object(self): """ Test that multiple attributes can be set on a given managed object. @@ -1750,6 +2101,10 @@ enums.CryptographicUsageMask.DECRYPT ] ) + sensitive = attribute_factory.create_attribute( + enums.AttributeType.SENSITIVE, + True + ) managed_object = pie_objects.SymmetricKey( enums.CryptographicAlgorithm.AES, 0, @@ -1764,6 +2119,7 @@ ) self.assertEqual(0, managed_object.cryptographic_length) self.assertEqual([], managed_object.cryptographic_usage_masks) + self.assertFalse(managed_object.sensitive) e._set_attribute_on_managed_object( managed_object, @@ -1802,6 +2158,13 @@ managed_object.cryptographic_usage_masks ) + e._set_attribute_on_managed_object( + managed_object, + ("Sensitive", sensitive.attribute_value) + ) + + self.assertTrue(managed_object.sensitive) + def test_set_attribute_on_managed_object_unsupported_features(self): """ Test that the right errors are generated when unsupported features @@ -1900,16 +2263,16 @@ ) # Test that an unsupported attribute cannot be set. - object_group = attribute_factory.create_attribute( - enums.AttributeType.OBJECT_GROUP, - 'Test Group' + custom_attribute = attribute_factory.create_attribute( + enums.AttributeType.CUSTOM_ATTRIBUTE, + "Test Group" ) args = ( managed_object, - ('Object Group', object_group.attribute_value) + ("Custom Attribute", custom_attribute.attribute_value) ) - regex = "The Object Group attribute is unsupported." + regex = "The Custom Attribute attribute is unsupported." six.assertRaisesRegex( self, exceptions.InvalidField, @@ -1918,700 +2281,970 @@ *args ) - def test_is_allowed_by_operation_policy_granted(self): + def test_set_attribute_on_managed_object_by_index(self): """ - Test that access granted by operation policy is processed correctly. + Test that an attribute can be modified on a managed object given its + name, index, and new value. """ e = engine.KmipEngine() - e.is_allowed = mock.Mock(return_value=True) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() - result = e._is_allowed_by_operation_policy( - 'test_policy', - ['test_user', ['test_group_A', 'test_group_B']], - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.GET + symmetric_key = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'', + masks=[enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.DECRYPT] ) - e.is_allowed.assert_called_once_with( - 'test_policy', - 'test_user', - 'test_group_A', - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.GET - ) - self.assertTrue(result) + e._data_session.add(symmetric_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - def test_is_allowed_by_operation_policy_denied(self): - """ - Test that access denied by operation policy is processed correctly. - """ - e = engine.KmipEngine() - e.is_allowed = mock.Mock(return_value=False) + e._set_attribute_on_managed_object( + symmetric_key, + ( + "Application Specific Information", + [ + attributes.ApplicationSpecificInformation( + application_namespace="Example Namespace 1", + application_data="Example Data 1" + ), + attributes.ApplicationSpecificInformation( + application_namespace="Example Namespace 2", + application_data="Example Data 2" + ) + ] + ) + ) + e._set_attribute_on_managed_object( + symmetric_key, + ( + "Name", + [ + attributes.Name( + name_value=attributes.Name.NameValue("Name 1") + ), + attributes.Name( + name_value=attributes.Name.NameValue("Name 2") + ) + ] + ) + ) + e._set_attribute_on_managed_object( + symmetric_key, + ( + "Object Group", + [ + primitives.TextString( + "Example Group 1", + tag=enums.Tags.OBJECT_GROUP + ), + primitives.TextString( + "Example Group 2", + tag=enums.Tags.OBJECT_GROUP + ) + ] + ) + ) - result = e._is_allowed_by_operation_policy( - 'test_policy', - ['test_user', ['test_group_A', 'test_group_B']], - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.GET + # Test setting an ApplicationSpecificInformation attribute by index + a = e._get_attribute_from_managed_object( + symmetric_key, + "Application Specific Information" + ) + self.assertEqual(2, len(a)) + self.assertEqual( + "Example Namespace 1", + a[0].get("application_namespace") + ) + self.assertEqual("Example Data 1", a[0].get("application_data")) + self.assertEqual( + "Example Namespace 2", + a[1].get("application_namespace") ) + self.assertEqual("Example Data 2", a[1].get("application_data")) + e._set_attribute_on_managed_object_by_index( + symmetric_key, + "Application Specific Information", + attributes.ApplicationSpecificInformation( + application_namespace="Example Namespace 3", + application_data="Example Data 3" + ), + 1 + ) + a = e._get_attribute_from_managed_object( + symmetric_key, + "Application Specific Information" + ) + self.assertEqual(2, len(a)) + self.assertEqual( + "Example Namespace 1", + a[0].get("application_namespace") + ) + self.assertEqual("Example Data 1", a[0].get("application_data")) + self.assertEqual( + "Example Namespace 3", + a[1].get("application_namespace") + ) + self.assertEqual("Example Data 3", a[1].get("application_data")) - e.is_allowed.assert_any_call( - 'test_policy', - 'test_user', - 'test_group_A', - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.GET + # Test setting a Name attribute by index + a = e._get_attribute_from_managed_object(symmetric_key, "Name") + self.assertEqual(3, len(a)) + self.assertEqual("Symmetric Key", a[0].name_value.value) + self.assertEqual("Name 1", a[1].name_value.value) + self.assertEqual("Name 2", a[2].name_value.value) + e._set_attribute_on_managed_object_by_index( + symmetric_key, + "Name", + attributes.Name( + name_value=attributes.Name.NameValue("Name 3") + ), + 1 ) - e.is_allowed.assert_any_call( - 'test_policy', - 'test_user', - 'test_group_B', - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.GET + a = e._get_attribute_from_managed_object(symmetric_key, "Name") + self.assertEqual(3, len(a)) + self.assertEqual("Symmetric Key", a[0].name_value.value) + self.assertEqual("Name 3", a[1].name_value.value) + self.assertEqual("Name 2", a[2].name_value.value) + + # Test setting an ObjectGroup attribute by index + a = e._get_attribute_from_managed_object(symmetric_key, "Object Group") + self.assertEqual(2, len(a)) + self.assertEqual("Example Group 1", a[0]) + self.assertEqual("Example Group 2", a[1]) + e._set_attribute_on_managed_object_by_index( + symmetric_key, + "Object Group", + primitives.TextString( + "Example Group 3", + tag=enums.Tags.OBJECT_GROUP + ), + 1 ) - self.assertFalse(result) + a = e._get_attribute_from_managed_object(symmetric_key, "Object Group") + self.assertEqual(2, len(a)) + self.assertEqual("Example Group 1", a[0]) + self.assertEqual("Example Group 3", a[1]) - def test_is_allowed_by_operation_policy_no_groups(self): + def test_delete_attribute_from_managed_object(self): """ - Test that access by operation policy is processed correctly when no - user groups are provided. + Test that various attributes can be deleted correctly from a given + managed object. """ e = engine.KmipEngine() - e.is_allowed = mock.Mock(return_value=True) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() - result = e._is_allowed_by_operation_policy( - 'test_policy', - ['test_user', None], - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.GET + attribute_factory = factory.AttributeFactory() + + name_1 = attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + "Name 1", + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ) + name_2 = attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + "Name 2", + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ) + app_specific_info_1 = attribute_factory.create_attribute( + enums.AttributeType.APPLICATION_SPECIFIC_INFORMATION, + { + "application_namespace": "Namespace 1", + "application_data": "Data 1" + } + ) + app_specific_info_2 = attribute_factory.create_attribute( + enums.AttributeType.APPLICATION_SPECIFIC_INFORMATION, + { + "application_namespace": "Namespace 2", + "application_data": "Data 2" + } + ) + object_group_1 = attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Object Group 1" + ) + object_group_2 = attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Object Group 2" + ) + managed_object = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' ) + managed_object.names.clear() - e.is_allowed.assert_called_once_with( - 'test_policy', - 'test_user', - None, - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.GET + self.assertEqual(0, len(managed_object.names)) + self.assertEqual(0, len(managed_object.app_specific_info)) + self.assertEqual(0, len(managed_object.object_groups)) + + e._set_attribute_on_managed_object( + managed_object, + ( + "Name", + [ + name_1.attribute_value, + name_2.attribute_value + ] + ) + ) + e._set_attribute_on_managed_object( + managed_object, + ( + "Application Specific Information", + [ + app_specific_info_1.attribute_value, + app_specific_info_2.attribute_value + ] + ) + ) + e._set_attribute_on_managed_object( + managed_object, + ( + "Object Group", + [ + object_group_1.attribute_value, + object_group_2.attribute_value + ] + ) ) - self.assertTrue(result) - def test_is_allowed_by_operation_policy_groups_empty(self): - """ - Test that access by operation policy is processed correctly when the - provided set of user groups is empty. + self.assertEqual(2, len(managed_object.names)) + self.assertEqual(2, len(managed_object.app_specific_info)) + self.assertEqual(2, len(managed_object.object_groups)) - Note that _is_allowed will always return True here, but because there - are no groups to check, access is by default denied. - """ - e = engine.KmipEngine() - e.is_allowed = mock.Mock(return_value=True) + e._delete_attribute_from_managed_object( + managed_object, + ( + "Application Specific Information", + 0, + None + ) + ) - result = e._is_allowed_by_operation_policy( - 'test_policy', - ['test_user', []], - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.GET + self.assertEqual(1, len(managed_object.app_specific_info)) + + e._delete_attribute_from_managed_object( + managed_object, + ( + "Application Specific Information", + 0, + app_specific_info_2.attribute_value + ) ) - e.is_allowed.assert_not_called() - self.assertFalse(result) + self.assertEqual(0, len(managed_object.app_specific_info)) - def test_get_relevant_policy_section_policy_missing(self): - """ - Test that the lookup for a non-existent policy is handled correctly. - """ - e = engine.KmipEngine() - e._operation_policies = {} - e._logger = mock.MagicMock() + e._delete_attribute_from_managed_object( + managed_object, + ( + "Name", + None, + primitives.TextString(value="Name 2", tag=enums.Tags.NAME) + ) + ) - result = e.get_relevant_policy_section('invalid') + self.assertEqual(1, len(managed_object.names)) + self.assertEqual("Name 1", managed_object.names[0]) - e._logger.warning.assert_called_once_with( - "The 'invalid' policy does not exist." + e._delete_attribute_from_managed_object( + managed_object, + ( + "Object Group", + None, + None + ) ) - self.assertIsNone(result) - def test_get_relevant_policy_section_no_group(self): - """ - Test that the lookup for a policy with no group specified is handled - correctly. - """ - e = engine.KmipEngine() - e._operation_policies = { - 'test_policy': { - 'preset': { - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_OWNER - } - } - } - } + self.assertEqual(0, len(managed_object.object_groups)) - expected = { - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_OWNER - } - } + e._set_attribute_on_managed_object( + managed_object, + ( + "Object Group", + [object_group_1.attribute_value] + ) + ) - result = e.get_relevant_policy_section('test_policy') - self.assertEqual(expected, result) + self.assertEqual(1, len(managed_object.object_groups)) - def test_get_relevant_policy_section_group(self): + e._delete_attribute_from_managed_object( + managed_object, + ( + "Object Group", + None, + primitives.TextString( + value="Object Group 1", + tag=enums.Tags.OBJECT_GROUP + ) + ) + ) + + self.assertEqual(0, len(managed_object.object_groups)) + + def test_delete_attribute_from_managed_object_unsupported_attribute(self): """ - Test that the lookup for a policy with a group specified is handled - correctly. + Test that an ItemNotFound error is raised when attempting to delete an + unsupported attribute from a managed object. """ e = engine.KmipEngine() - e._operation_policies = { - 'test_policy': { - 'preset': { - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_OWNER - } - }, - 'groups': { - 'test_group': { - enums.ObjectType.CERTIFICATE: { - enums.Operation.CREATE: enums.Policy.ALLOW_ALL - } - } - } - } - } - - expected = { - enums.ObjectType.CERTIFICATE: { - enums.Operation.CREATE: enums.Policy.ALLOW_ALL - } - } + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() - result = e.get_relevant_policy_section('test_policy', 'test_group') - self.assertEqual(expected, result) + managed_object = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + args = (managed_object, ("Digital Signature Algorithm", None, None)) + self.assertRaisesRegex( + exceptions.ItemNotFound, + "The 'Digital Signature Algorithm' attribute is not applicable " + "to 'SymmetricKey' objects.", + e._delete_attribute_from_managed_object, + *args + ) - def test_get_relevant_policy_section_group_not_supported(self): + def test_delete_attribute_from_managed_object_undeletable_attribute(self): """ - Test that the lookup for a policy with a group specified but not - supported is handled correctly. + Test that a PermissionDenied error is raised when attempting to delete + a required attribute from a managed object. """ e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() e._logger = mock.MagicMock() - e._operation_policies = { - 'test_policy': { - 'preset': { - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_OWNER - } - }, - 'groups': { - 'test_group_B': { - enums.ObjectType.CERTIFICATE: { - enums.Operation.CREATE: enums.Policy.ALLOW_ALL - } - } - } - } - } - - result = e.get_relevant_policy_section('test_policy', 'test_group_A') - e._logger.debug.assert_called_once_with( - "The 'test_policy' policy does not support group 'test_group_A'." + managed_object = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + args = (managed_object, ("Cryptographic Algorithm", None, None)) + self.assertRaisesRegex( + exceptions.PermissionDenied, + "Cannot delete a required attribute.", + e._delete_attribute_from_managed_object, + *args ) - self.assertIsNone(result) - def test_get_relevant_policy_section_groups_not_supported(self): + def test_delete_attribute_from_managed_object_unsupported_multivalue(self): """ - Test that the lookup for a group-less policy with a group specified is - handled correctly. + Test that an InvalidField error is raised when attempting to delete an + unsupported multivalued attribute. """ + # TODO (peterhamilton) Remove this test once all multivalued attributes + # are supported. e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() e._logger = mock.MagicMock() - e._operation_policies = { - 'test_policy': { - 'preset': { - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_OWNER - } - } - } - } - - result = e.get_relevant_policy_section('test_policy', 'test_group_A') - e._logger.debug.assert_called_once_with( - "The 'test_policy' policy does not support groups." + managed_object = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + args = (managed_object, ("Link", None, None)) + self.assertRaisesRegex( + exceptions.InvalidField, + "The 'Link' attribute is not supported.", + e._delete_attribute_from_managed_object, + *args ) - self.assertIsNone(result) - def test_is_allowed_policy_not_found(self): + def test_delete_attribute_from_managed_object_bad_attribute_value(self): """ - Test that an access check using a non-existent policy is handled - correctly. + Test that an ItemNotFound error is raised when attempting to delete + an attribute by value that cannot be found on a managed object. """ e = engine.KmipEngine() - e.get_relevant_policy_section = mock.Mock(return_value=None) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() - result = e.is_allowed( - 'test_policy', - 'test_user', - 'test_group', - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.GET + managed_object = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + args = ( + managed_object, + ( + "Object Group", + None, + primitives.TextString( + value="invalid", + tag=enums.Tags.OBJECT_GROUP + ) + ) + ) + self.assertRaisesRegex( + exceptions.ItemNotFound, + "Could not locate the attribute instance with the specified " + "value: {'object_group': 'invalid'}", + e._delete_attribute_from_managed_object, + *args ) - self.assertFalse(result) - def test_is_allowed_policy_object_type_mismatch(self): + def test_delete_attribute_from_managed_object_bad_attribute_index(self): """ - Test that an access check using a policy that does not support the - specified object type is handled correctly. + Test that an ItemNotFound error is raised when attempting to delete + an attribute by index that cannot be found on a managed object. """ e = engine.KmipEngine() - e._logger = mock.Mock() - e._get_enum_string = mock.Mock(return_value="Certificate") - e.get_relevant_policy_section = mock.Mock( - return_value={ - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_OWNER - } - } - ) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() - result = e.is_allowed( - 'test_policy', - 'test_user', - 'test_group', - 'test_user', - enums.ObjectType.CERTIFICATE, - enums.Operation.GET + managed_object = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' ) - - e._logger.warning.assert_called_once_with( - "The 'test_policy' policy does not apply to Certificate objects." + args = ( + managed_object, + ( + "Object Group", + 3, + None + ) + ) + self.assertRaisesRegex( + exceptions.ItemNotFound, + "Could not locate the attribute instance with the specified " + "index: 3", + e._delete_attribute_from_managed_object, + *args ) - self.assertFalse(result) - def test_is_allowed_policy_operation_mismatch(self): + def test_delete_attribute_from_managed_object_bad_single_value(self): """ - Test that an access check using a policy that does not support the - specified operation is handled correctly. + Test that an InvalidField error is raised when attempting to delete + a single-valued attribute from a managed object. """ e = engine.KmipEngine() - e._logger = mock.Mock() - e._get_enum_string = mock.Mock(side_effect=["Create", "SymmetricKey"]) - e.get_relevant_policy_section = mock.Mock( - return_value={ - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_OWNER - } - } - ) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() - result = e.is_allowed( - 'test_policy', - 'test_user', - 'test_group', - 'test_user', - enums.ObjectType.SYMMETRIC_KEY, - enums.Operation.CREATE + managed_object = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' ) - - e._logger.warning.assert_called_once_with( - "The 'test_policy' policy does not apply to Create operations on " - "SymmetricKey objects." + args = ( + managed_object, + ( + "Contact Information", + None, + None + ) + ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The 'Contact Information' attribute is not supported", + e._delete_attribute_from_managed_object, + *args ) - self.assertFalse(result) - def test_is_allowed_allow_all(self): + def test_is_allowed_by_operation_policy_granted(self): """ - Test that an access check resulting in an "Allow All" policy is - processed correctly. + Test that access granted by operation policy is processed correctly. """ e = engine.KmipEngine() - e.get_relevant_policy_section = mock.Mock( - return_value={ - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_ALL - } - } + e.is_allowed = mock.Mock(return_value=True) + + result = e._is_allowed_by_operation_policy( + 'test_policy', + ['test_user', ['test_group_A', 'test_group_B']], + 'test_user', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.GET ) - result = e.is_allowed( + e.is_allowed.assert_called_once_with( 'test_policy', 'test_user', - 'test_group', + 'test_group_A', 'test_user', enums.ObjectType.SYMMETRIC_KEY, enums.Operation.GET ) self.assertTrue(result) - def test_is_allowed_allow_owner(self): + def test_is_allowed_by_operation_policy_denied(self): """ - Test that an access check resulting in an "Allow Owner" policy is - processed correctly. + Test that access denied by operation policy is processed correctly. """ e = engine.KmipEngine() - e.get_relevant_policy_section = mock.Mock( - return_value={ - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_OWNER - } - } - ) + e.is_allowed = mock.Mock(return_value=False) - result = e.is_allowed( + result = e._is_allowed_by_operation_policy( 'test_policy', - 'test_user', - 'test_group', + ['test_user', ['test_group_A', 'test_group_B']], 'test_user', enums.ObjectType.SYMMETRIC_KEY, enums.Operation.GET ) - self.assertTrue(result) - def test_is_allowed_allow_owner_not_owner(self): - """ - Test that an access check resulting in an "Allow Owner" policy is - processed correctly when the user requesting access is not the owner. - """ - e = engine.KmipEngine() - e.get_relevant_policy_section = mock.Mock( - return_value={ - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.ALLOW_OWNER - } - } + e.is_allowed.assert_any_call( + 'test_policy', + 'test_user', + 'test_group_A', + 'test_user', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.GET ) - - result = e.is_allowed( + e.is_allowed.assert_any_call( 'test_policy', - 'test_user_A', - 'test_group', - 'test_user_B', + 'test_user', + 'test_group_B', + 'test_user', enums.ObjectType.SYMMETRIC_KEY, enums.Operation.GET ) self.assertFalse(result) - def test_is_allowed_disallow_all(self): + def test_is_allowed_by_operation_policy_no_groups(self): """ - Test that an access check resulting in an "Disallow All" policy is - processed correctly. + Test that access by operation policy is processed correctly when no + user groups are provided. """ e = engine.KmipEngine() - e.get_relevant_policy_section = mock.Mock( - return_value={ - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: enums.Policy.DISALLOW_ALL - } - } + e.is_allowed = mock.Mock(return_value=True) + + result = e._is_allowed_by_operation_policy( + 'test_policy', + ['test_user', None], + 'test_user', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.GET ) - result = e.is_allowed( + e.is_allowed.assert_called_once_with( 'test_policy', 'test_user', - 'test_group', + None, 'test_user', enums.ObjectType.SYMMETRIC_KEY, enums.Operation.GET ) - self.assertFalse(result) + self.assertTrue(result) - def test_is_allowed_invalid_permission(self): + def test_is_allowed_by_operation_policy_groups_empty(self): """ - Test that an access check resulting in an invalid policy option is - processed correctly. + Test that access by operation policy is processed correctly when the + provided set of user groups is empty. + + Note that _is_allowed will always return True here, but because there + are no groups to check, access is by default denied. """ e = engine.KmipEngine() - e.get_relevant_policy_section = mock.Mock( - return_value={ - enums.ObjectType.SYMMETRIC_KEY: { - enums.Operation.GET: 'invalid' - } - } - ) + e.is_allowed = mock.Mock(return_value=True) - result = e.is_allowed( + result = e._is_allowed_by_operation_policy( 'test_policy', - 'test_user', - 'test_group', + ['test_user', []], 'test_user', enums.ObjectType.SYMMETRIC_KEY, enums.Operation.GET ) + + e.is_allowed.assert_not_called() self.assertFalse(result) - def test_get_object_with_access_controls(self): + def test_get_relevant_policy_section_policy_missing(self): """ - Test that an unallowed object access request is handled correctly. + Test that the lookup for a non-existent policy is handled correctly. """ e = engine.KmipEngine() - e._data_store = self.engine - e._data_store_session_factory = self.session_factory - e._data_session = e._data_store_session_factory() - e._is_allowed_by_operation_policy = mock.Mock(return_value=False) + e._operation_policies = {} e._logger = mock.MagicMock() - e._client_identity = 'test' - - obj_a = pie_objects.OpaqueObject(b'', enums.OpaqueDataType.NONE) - obj_a._owner = 'admin' - - e._data_session.add(obj_a) - e._data_session.commit() - e._data_session = e._data_store_session_factory() - id_a = str(obj_a.unique_identifier) + result = e.get_relevant_policy_section('invalid') - # Test by specifying the ID of the object to retrieve and the - # operation context. - args = [id_a, enums.Operation.GET] - six.assertRaisesRegex( - self, - exceptions.PermissionDenied, - "Could not locate object: {0}".format(id_a), - e._get_object_with_access_controls, - *args + e._logger.warning.assert_called_once_with( + "The 'invalid' policy does not exist." ) + self.assertIsNone(result) - def test_create(self): + def test_get_relevant_policy_section_no_group(self): """ - Test that a Create request can be processed correctly. + Test that the lookup for a policy with no group specified is handled + correctly. """ e = engine.KmipEngine() - e._data_store = self.engine - e._data_store_session_factory = self.session_factory - e._data_session = e._data_store_session_factory() - e._logger = mock.MagicMock() - - attribute_factory = factory.AttributeFactory() - - # Build Create request - object_type = enums.ObjectType.SYMMETRIC_KEY - template_attribute = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.NAME, - attributes.Name.create( - 'Test Symmetric Key', - enums.NameType.UNINTERPRETED_TEXT_STRING - ) - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.AES - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 256 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, - [ - enums.CryptographicUsageMask.ENCRYPT, - enums.CryptographicUsageMask.DECRYPT - ] - ), - attribute_factory.create_attribute( - enums.AttributeType.OPERATION_POLICY_NAME, - 'test' - ) - ] - ) - payload = payloads.CreateRequestPayload( - object_type, - template_attribute - ) + e._operation_policies = { + 'test_policy': { + 'preset': { + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_OWNER + } + } + } + } - response_payload = e._process_create(payload) - e._data_session.commit() - e._data_session = e._data_store_session_factory() + expected = { + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_OWNER + } + } - e._logger.info.assert_any_call( - "Processing operation: Create" - ) + result = e.get_relevant_policy_section('test_policy') + self.assertEqual(expected, result) - uid = response_payload.unique_identifier - self.assertEqual('1', uid) + def test_get_relevant_policy_section_group(self): + """ + Test that the lookup for a policy with a group specified is handled + correctly. + """ + e = engine.KmipEngine() + e._operation_policies = { + 'test_policy': { + 'preset': { + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_OWNER + } + }, + 'groups': { + 'test_group': { + enums.ObjectType.CERTIFICATE: { + enums.Operation.CREATE: enums.Policy.ALLOW_ALL + } + } + } + } + } - # Retrieve the stored object and verify all attributes were set - # appropriately. - symmetric_key = e._data_session.query( - pie_objects.SymmetricKey - ).filter( - pie_objects.ManagedObject.unique_identifier == uid - ).one() - self.assertEqual( - enums.KeyFormatType.RAW, - symmetric_key.key_format_type - ) - self.assertEqual(1, len(symmetric_key.names)) - self.assertIn('Test Symmetric Key', symmetric_key.names) - self.assertEqual(256, len(symmetric_key.value) * 8) - self.assertEqual( - enums.CryptographicAlgorithm.AES, - symmetric_key.cryptographic_algorithm - ) - self.assertEqual(256, symmetric_key.cryptographic_length) - self.assertEqual(2, len(symmetric_key.cryptographic_usage_masks)) - self.assertIn( - enums.CryptographicUsageMask.ENCRYPT, - symmetric_key.cryptographic_usage_masks - ) - self.assertIn( - enums.CryptographicUsageMask.DECRYPT, - symmetric_key.cryptographic_usage_masks - ) - self.assertEqual('test', symmetric_key.operation_policy_name) - self.assertIsNotNone(symmetric_key.initial_date) - self.assertNotEqual(0, symmetric_key.initial_date) + expected = { + enums.ObjectType.CERTIFICATE: { + enums.Operation.CREATE: enums.Policy.ALLOW_ALL + } + } - self.assertEqual(uid, e._id_placeholder) + result = e.get_relevant_policy_section('test_policy', 'test_group') + self.assertEqual(expected, result) - def test_create_unsupported_object_type(self): + def test_get_relevant_policy_section_group_not_supported(self): """ - Test that an InvalidField error is generated when attempting to - create an unsupported object type. + Test that the lookup for a policy with a group specified but not + supported is handled correctly. """ e = engine.KmipEngine() - e._data_store = self.engine - e._data_store_session_factory = self.session_factory - e._data_session = e._data_store_session_factory() e._logger = mock.MagicMock() + e._operation_policies = { + 'test_policy': { + 'preset': { + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_OWNER + } + }, + 'groups': { + 'test_group_B': { + enums.ObjectType.CERTIFICATE: { + enums.Operation.CREATE: enums.Policy.ALLOW_ALL + } + } + } + } + } - object_type = enums.ObjectType.PUBLIC_KEY - payload = payloads.CreateRequestPayload( - object_type - ) - - args = (payload, ) - regex = "Cannot create a PublicKey object with the Create operation." - six.assertRaisesRegex( - self, - exceptions.InvalidField, - regex, - e._process_create, - *args - ) + result = e.get_relevant_policy_section('test_policy', 'test_group_A') - e._logger.info.assert_any_call( - "Processing operation: Create" + e._logger.debug.assert_called_once_with( + "The 'test_policy' policy does not support group 'test_group_A'." ) + self.assertIsNone(result) - def test_create_omitting_attributes(self): + def test_get_relevant_policy_section_groups_not_supported(self): """ - Test that InvalidField errors are generated when trying to create - a symmetric key without required attributes. + Test that the lookup for a group-less policy with a group specified is + handled correctly. """ e = engine.KmipEngine() - e._data_store = self.engine - e._data_store_session_factory = self.session_factory - e._data_session = e._data_store_session_factory() e._logger = mock.MagicMock() + e._operation_policies = { + 'test_policy': { + 'preset': { + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_OWNER + } + } + } + } - attribute_factory = factory.AttributeFactory() + result = e.get_relevant_policy_section('test_policy', 'test_group_A') - # Test the error for omitting the Cryptographic Algorithm - object_type = enums.ObjectType.SYMMETRIC_KEY - template_attribute = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.NAME, - attributes.Name.create( - 'Test Symmetric Key', - enums.NameType.UNINTERPRETED_TEXT_STRING - ) - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 256 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, - [ - enums.CryptographicUsageMask.ENCRYPT, - enums.CryptographicUsageMask.DECRYPT - ] - ) - ] - ) - payload = payloads.CreateRequestPayload( - object_type, - template_attribute + e._logger.debug.assert_called_once_with( + "The 'test_policy' policy does not support groups." ) + self.assertIsNone(result) - args = (payload, ) - regex = ( - "The cryptographic algorithm must be specified as an attribute." - ) - six.assertRaisesRegex( - self, - exceptions.InvalidField, - regex, - e._process_create, - *args - ) + def test_is_allowed_policy_not_found(self): + """ + Test that an access check using a non-existent policy is handled + correctly. + """ + e = engine.KmipEngine() + e.get_relevant_policy_section = mock.Mock(return_value=None) - e._logger.info.assert_any_call( - "Processing operation: Create" + result = e.is_allowed( + 'test_policy', + 'test_user', + 'test_group', + 'test_user', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.GET ) - e._logger.reset_mock() + self.assertFalse(result) - # Test the error for omitting the Cryptographic Length - object_type = enums.ObjectType.SYMMETRIC_KEY - template_attribute = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.NAME, - attributes.Name.create( - 'Test Symmetric Key', - enums.NameType.UNINTERPRETED_TEXT_STRING - ) - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.AES - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, - [ - enums.CryptographicUsageMask.ENCRYPT, - enums.CryptographicUsageMask.DECRYPT - ] - ) - ] + def test_is_allowed_policy_object_type_mismatch(self): + """ + Test that an access check using a policy that does not support the + specified object type is handled correctly. + """ + e = engine.KmipEngine() + e._logger = mock.Mock() + e._get_enum_string = mock.Mock(return_value="Certificate") + e.get_relevant_policy_section = mock.Mock( + return_value={ + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_OWNER + } + } ) - payload = payloads.CreateRequestPayload( - object_type, - template_attribute + + result = e.is_allowed( + 'test_policy', + 'test_user', + 'test_group', + 'test_user', + enums.ObjectType.CERTIFICATE, + enums.Operation.GET ) - args = (payload, ) - regex = ( - "The cryptographic length must be specified as an attribute." + e._logger.warning.assert_called_once_with( + "The 'test_policy' policy does not apply to Certificate objects." + ) + self.assertFalse(result) + + def test_is_allowed_policy_operation_mismatch(self): + """ + Test that an access check using a policy that does not support the + specified operation is handled correctly. + """ + e = engine.KmipEngine() + e._logger = mock.Mock() + e._get_enum_string = mock.Mock(side_effect=["Create", "SymmetricKey"]) + e.get_relevant_policy_section = mock.Mock( + return_value={ + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_OWNER + } + } + ) + + result = e.is_allowed( + 'test_policy', + 'test_user', + 'test_group', + 'test_user', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.CREATE + ) + + e._logger.warning.assert_called_once_with( + "The 'test_policy' policy does not apply to Create operations on " + "SymmetricKey objects." + ) + self.assertFalse(result) + + def test_is_allowed_allow_all(self): + """ + Test that an access check resulting in an "Allow All" policy is + processed correctly. + """ + e = engine.KmipEngine() + e.get_relevant_policy_section = mock.Mock( + return_value={ + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_ALL + } + } + ) + + result = e.is_allowed( + 'test_policy', + 'test_user', + 'test_group', + 'test_user', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.GET + ) + self.assertTrue(result) + + def test_is_allowed_allow_owner(self): + """ + Test that an access check resulting in an "Allow Owner" policy is + processed correctly. + """ + e = engine.KmipEngine() + e.get_relevant_policy_section = mock.Mock( + return_value={ + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_OWNER + } + } + ) + + result = e.is_allowed( + 'test_policy', + 'test_user', + 'test_group', + 'test_user', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.GET + ) + self.assertTrue(result) + + def test_is_allowed_allow_owner_not_owner(self): + """ + Test that an access check resulting in an "Allow Owner" policy is + processed correctly when the user requesting access is not the owner. + """ + e = engine.KmipEngine() + e.get_relevant_policy_section = mock.Mock( + return_value={ + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.ALLOW_OWNER + } + } + ) + + result = e.is_allowed( + 'test_policy', + 'test_user_A', + 'test_group', + 'test_user_B', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.GET + ) + self.assertFalse(result) + + def test_is_allowed_disallow_all(self): + """ + Test that an access check resulting in an "Disallow All" policy is + processed correctly. + """ + e = engine.KmipEngine() + e.get_relevant_policy_section = mock.Mock( + return_value={ + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: enums.Policy.DISALLOW_ALL + } + } + ) + + result = e.is_allowed( + 'test_policy', + 'test_user', + 'test_group', + 'test_user', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.GET + ) + self.assertFalse(result) + + def test_is_allowed_invalid_permission(self): + """ + Test that an access check resulting in an invalid policy option is + processed correctly. + """ + e = engine.KmipEngine() + e.get_relevant_policy_section = mock.Mock( + return_value={ + enums.ObjectType.SYMMETRIC_KEY: { + enums.Operation.GET: 'invalid' + } + } + ) + + result = e.is_allowed( + 'test_policy', + 'test_user', + 'test_group', + 'test_user', + enums.ObjectType.SYMMETRIC_KEY, + enums.Operation.GET ) + self.assertFalse(result) + + def test_get_object_with_access_controls(self): + """ + Test that an unallowed object access request is handled correctly. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=False) + e._logger = mock.MagicMock() + e._client_identity = 'test' + + obj_a = pie_objects.OpaqueObject(b'', enums.OpaqueDataType.NONE) + obj_a._owner = 'admin' + + e._data_session.add(obj_a) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + id_a = str(obj_a.unique_identifier) + + # Test by specifying the ID of the object to retrieve and the + # operation context. + args = [id_a, enums.Operation.GET] six.assertRaisesRegex( self, - exceptions.InvalidField, - regex, - e._process_create, + exceptions.PermissionDenied, + "Could not locate object: {0}".format(id_a), + e._get_object_with_access_controls, *args ) - e._logger.info.assert_any_call( - "Processing operation: Create" - ) - e._logger.reset_mock() + def test_create(self): + """ + Test that a Create request can be processed correctly. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() - # Test the error for omitting the Cryptographic Usage Mask + attribute_factory = factory.AttributeFactory() + + # Build Create request object_type = enums.ObjectType.SYMMETRIC_KEY template_attribute = objects.TemplateAttribute( attributes=[ @@ -2629,16 +3262,255 @@ attribute_factory.create_attribute( enums.AttributeType.CRYPTOGRAPHIC_LENGTH, 256 - ) - ] - ) - payload = payloads.CreateRequestPayload( - object_type, - template_attribute - ) - - args = (payload, ) - regex = ( + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.DECRYPT + ] + ), + attribute_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + 'test' + ), + attribute_factory.create_attribute( + enums.AttributeType.APPLICATION_SPECIFIC_INFORMATION, + { + "application_namespace": "ssl", + "application_data": "www.example.com" + } + ), + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Group1" + ) + ] + ) + payload = payloads.CreateRequestPayload( + object_type, + template_attribute + ) + + response_payload = e._process_create(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: Create" + ) + + uid = response_payload.unique_identifier + self.assertEqual('1', uid) + + # Retrieve the stored object and verify all attributes were set + # appropriately. + symmetric_key = e._data_session.query( + pie_objects.SymmetricKey + ).filter( + pie_objects.ManagedObject.unique_identifier == uid + ).one() + self.assertEqual( + enums.KeyFormatType.RAW, + symmetric_key.key_format_type + ) + self.assertEqual(1, len(symmetric_key.names)) + self.assertIn('Test Symmetric Key', symmetric_key.names) + self.assertEqual(256, len(symmetric_key.value) * 8) + self.assertEqual( + enums.CryptographicAlgorithm.AES, + symmetric_key.cryptographic_algorithm + ) + self.assertEqual(256, symmetric_key.cryptographic_length) + self.assertEqual(2, len(symmetric_key.cryptographic_usage_masks)) + self.assertIn( + enums.CryptographicUsageMask.ENCRYPT, + symmetric_key.cryptographic_usage_masks + ) + self.assertIn( + enums.CryptographicUsageMask.DECRYPT, + symmetric_key.cryptographic_usage_masks + ) + self.assertEqual('test', symmetric_key.operation_policy_name) + self.assertIsNotNone(symmetric_key.initial_date) + self.assertNotEqual(0, symmetric_key.initial_date) + self.assertEqual(1, len(symmetric_key.app_specific_info)) + self.assertEqual( + "ssl", + symmetric_key.app_specific_info[0].application_namespace + ) + self.assertEqual( + "www.example.com", + symmetric_key.app_specific_info[0].application_data + ) + self.assertEqual(1, len(symmetric_key.object_groups)) + self.assertEqual("Group1", symmetric_key.object_groups[0].object_group) + + self.assertEqual(uid, e._id_placeholder) + + def test_create_unsupported_object_type(self): + """ + Test that an InvalidField error is generated when attempting to + create an unsupported object type. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() + + object_type = enums.ObjectType.PUBLIC_KEY + payload = payloads.CreateRequestPayload( + object_type + ) + + args = (payload, ) + regex = "Cannot create a PublicKey object with the Create operation." + six.assertRaisesRegex( + self, + exceptions.InvalidField, + regex, + e._process_create, + *args + ) + + e._logger.info.assert_any_call( + "Processing operation: Create" + ) + + def test_create_omitting_attributes(self): + """ + Test that InvalidField errors are generated when trying to create + a symmetric key without required attributes. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() + + attribute_factory = factory.AttributeFactory() + + # Test the error for omitting the Cryptographic Algorithm + object_type = enums.ObjectType.SYMMETRIC_KEY + template_attribute = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + 'Test Symmetric Key', + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 256 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.DECRYPT + ] + ) + ] + ) + payload = payloads.CreateRequestPayload( + object_type, + template_attribute + ) + + args = (payload, ) + regex = ( + "The cryptographic algorithm must be specified as an attribute." + ) + six.assertRaisesRegex( + self, + exceptions.InvalidField, + regex, + e._process_create, + *args + ) + + e._logger.info.assert_any_call( + "Processing operation: Create" + ) + e._logger.reset_mock() + + # Test the error for omitting the Cryptographic Length + object_type = enums.ObjectType.SYMMETRIC_KEY + template_attribute = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + 'Test Symmetric Key', + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.DECRYPT + ] + ) + ] + ) + payload = payloads.CreateRequestPayload( + object_type, + template_attribute + ) + + args = (payload, ) + regex = ( + "The cryptographic length must be specified as an attribute." + ) + six.assertRaisesRegex( + self, + exceptions.InvalidField, + regex, + e._process_create, + *args + ) + + e._logger.info.assert_any_call( + "Processing operation: Create" + ) + e._logger.reset_mock() + + # Test the error for omitting the Cryptographic Usage Mask + object_type = enums.ObjectType.SYMMETRIC_KEY + template_attribute = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + 'Test Symmetric Key', + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 256 + ) + ] + ) + payload = payloads.CreateRequestPayload( + object_type, + template_attribute + ) + + args = (payload, ) + regex = ( "The cryptographic usage mask must be specified as an attribute." ) six.assertRaisesRegex( @@ -3140,400 +4012,2269 @@ *args ) e._logger.info.assert_any_call( - "Processing operation: CreateKeyPair" + "Processing operation: CreateKeyPair" + ) + e._logger.reset_mock() + + # Test that a missing PrivateKey CryptographicUsageMask raises an error + common_template = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + 'Test Asymmetric Key', + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ) + ], + tag=enums.Tags.COMMON_TEMPLATE_ATTRIBUTE + ) + public_template = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.RSA + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 2048 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT + ] + ) + ], + tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE + ) + private_template = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.RSA + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 2048 + ) + ], + tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE + ) + payload = payloads.CreateKeyPairRequestPayload( + common_template, + private_template, + public_template + ) + + args = (payload, ) + regex = ( + "The cryptographic usage mask must be specified as an attribute " + "for the private key." + ) + six.assertRaisesRegex( + self, + exceptions.InvalidField, + regex, + e._process_create_key_pair, + *args + ) + e._logger.info.assert_any_call( + "Processing operation: CreateKeyPair" + ) + e._logger.reset_mock() + + def test_create_key_pair_mismatched_attributes(self): + """ + Test that the right errors are generated when required attributes + are mismatched in a CreateKeyPair request. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() + + attribute_factory = factory.AttributeFactory() + + # Test that mismatched CryptographicAlgorithms raise an error. + common_template = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + 'Test Asymmetric Key', + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ) + ], + tag=enums.Tags.COMMON_TEMPLATE_ATTRIBUTE + ) + public_template = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.RSA + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 2048 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT + ] + ) + ], + tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE + ) + private_template = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.DSA + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 2048 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.DECRYPT + ] + ) + ], + tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE + ) + payload = payloads.CreateKeyPairRequestPayload( + common_template, + private_template, + public_template + ) + + args = (payload, ) + regex = ( + "The public and private key algorithms must be the same." + ) + six.assertRaisesRegex( + self, + exceptions.InvalidField, + regex, + e._process_create_key_pair, + *args + ) + e._logger.info.assert_any_call( + "Processing operation: CreateKeyPair" + ) + e._logger.reset_mock() + + # Test that mismatched CryptographicAlgorithms raise an error. + common_template = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + 'Test Asymmetric Key', + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ) + ], + tag=enums.Tags.COMMON_TEMPLATE_ATTRIBUTE + ) + public_template = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.RSA + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 2048 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT + ] + ) + ], + tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE + ) + private_template = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.RSA + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 4096 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.DECRYPT + ] + ) + ], + tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE + ) + payload = payloads.CreateKeyPairRequestPayload( + common_template, + private_template, + public_template + ) + + args = (payload, ) + regex = ( + "The public and private key lengths must be the same." + ) + six.assertRaisesRegex( + self, + exceptions.InvalidField, + regex, + e._process_create_key_pair, + *args + ) + e._logger.info.assert_any_call( + "Processing operation: CreateKeyPair" + ) + e._logger.reset_mock() + + def test_delete_attribute(self): + """ + Test that a DeleteAttribute request can be processed correctly. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + attribute_factory = factory.AttributeFactory() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + object_group_1 = attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Object Group 1" + ) + object_group_2 = attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Object Group 2" + ) + + e._data_session.add(secret) + e._set_attribute_on_managed_object( + secret, + ( + "Object Group", + [ + object_group_1.attribute_value, + object_group_2.attribute_value + ] + ) + ) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + # Confirm that the attribute was actually added by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + "1", + enums.Operation.DELETE_ATTRIBUTE + ) + self.assertEqual(2, len(managed_object.object_groups)) + + payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_name="Object Group", + attribute_index=1 + ) + + response_payload = e._process_delete_attribute(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: DeleteAttribute" + ) + self.assertEqual( + "1", + response_payload.unique_identifier + ) + self.assertEqual( + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Object Group 2", + 1 + ), + response_payload.attribute + ) + + # Confirm that the attribute was actually deleted by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + response_payload.unique_identifier, + enums.Operation.DELETE_ATTRIBUTE + ) + + self.assertEqual(1, len(managed_object.object_groups)) + payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_name="Object Group" + ) + + response_payload = e._process_delete_attribute(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: DeleteAttribute" + ) + self.assertEqual( + "1", + response_payload.unique_identifier + ) + self.assertEqual( + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Object Group 1", + 0 + ), + response_payload.attribute + ) + + # Confirm that the attribute was actually deleted by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + response_payload.unique_identifier, + enums.Operation.DELETE_ATTRIBUTE + ) + self.assertEqual(0, len(managed_object.object_groups)) + + def test_delete_attribute_with_kmip_2_0(self): + """ + Test that a DeleteAttribute request can be processed correctly + when using KMIP 2.0 payload features. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + attribute_factory = factory.AttributeFactory() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + object_group = attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Object Group 1" + ) + + e._data_session.add(secret) + e._set_attribute_on_managed_object( + secret, + ( + "Object Group", + [object_group.attribute_value] + ) + ) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + # Confirm that the attribute was actually added by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + "1", + enums.Operation.DELETE_ATTRIBUTE + ) + self.assertEqual(1, len(managed_object.names)) + self.assertEqual(1, len(managed_object.object_groups)) + + payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + current_attribute=objects.CurrentAttribute( + attribute=primitives.TextString( + value="Object Group 1", + tag=enums.Tags.OBJECT_GROUP + ) + ), + attribute_reference=objects.AttributeReference( + vendor_identification="Vendor 1", + attribute_name="Object Group" + ) + ) + + response_payload = e._process_delete_attribute(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: DeleteAttribute" + ) + self.assertEqual( + "1", + response_payload.unique_identifier + ) + self.assertIsNone(response_payload.attribute) + + # Confirm that the attribute was actually deleted by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + response_payload.unique_identifier, + enums.Operation.DELETE_ATTRIBUTE + ) + self.assertEqual(0, len(managed_object.object_groups)) + + payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_reference=objects.AttributeReference( + vendor_identification="Vendor 1", + attribute_name="Name" + ) + ) + + response_payload = e._process_delete_attribute(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: DeleteAttribute" + ) + self.assertEqual( + "1", + response_payload.unique_identifier + ) + self.assertIsNone(response_payload.attribute) + + # Confirm that the attribute was actually deleted by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + response_payload.unique_identifier, + enums.Operation.DELETE_ATTRIBUTE + ) + self.assertEqual(0, len(managed_object.names)) + + def test_delete_attribute_with_invalid_current_attribute(self): + """ + Test that an ItemNotFound error is raised when attempting to delete + an invalid current attribute from a managed object. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + current_attribute = objects.CurrentAttribute() + current_attribute._attribute = primitives.TextString( + value="Object Group 1", + tag=enums.Tags.CURRENT_ATTRIBUTE + ) + args = ( + payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + current_attribute=current_attribute + ), + ) + + self.assertRaisesRegex( + exceptions.ItemNotFound, + "No attribute with the specified name exists.", + e._process_delete_attribute, + *args + ) + + def test_delete_attribute_with_missing_current_attribute_reference(self): + """ + Test that an InvalidMessage error is raised when attempting to delete + an attribute without specifying a current attribute or an attribute + reference (under KMIP 2.0+). + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + args = ( + payloads.DeleteAttributeRequestPayload(unique_identifier="1"), + ) + + self.assertRaisesRegex( + exceptions.InvalidMessage, + "The DeleteAttribute request must specify the current attribute " + "or an attribute reference.", + e._process_delete_attribute, + *args + ) + + def test_delete_attribute_with_missing_attribute_name(self): + """ + Test that an InvalidMessage error is raised when attempting to delete + an attribute without specifying the attribute name (under KMIP 1.0+). + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + args = ( + payloads.DeleteAttributeRequestPayload(unique_identifier="1"), + ) + + self.assertRaisesRegex( + exceptions.InvalidMessage, + "The DeleteAttribute request must specify the attribute name.", + e._process_delete_attribute, + *args + ) + + def test_delete_attribute_with_invalid_attribute_index(self): + """ + Test that an ItemNotFound error is raised when attempting to delete + an attribute when specifying an invalid attribute index. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + args = ( + payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_name="Name", + attribute_index=20), + ) + + self.assertRaisesRegex( + exceptions.ItemNotFound, + "Could not locate the attribute instance with the specified " + "index: 20", + e._process_delete_attribute, + *args + ) + + def test_set_attribute(self): + """ + Test that a SetAttribute request can be processed correctly. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + # Confirm that the attribute is set to its default value by + # fetching the managed object fresh from the database and + # checking it. + managed_object = e._get_object_with_access_controls( + "1", + enums.Operation.SET_ATTRIBUTE + ) + self.assertFalse(managed_object.sensitive) + + payload = payloads.SetAttributeRequestPayload( + unique_identifier="1", + new_attribute=objects.NewAttribute( + attribute=primitives.Boolean( + value=True, + tag=enums.Tags.SENSITIVE + ) + ) + ) + + response_payload = e._process_set_attribute(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: SetAttribute" + ) + self.assertEqual( + "1", + response_payload.unique_identifier + ) + + # Confirm that the attribute was actually set by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + response_payload.unique_identifier, + enums.Operation.SET_ATTRIBUTE + ) + self.assertTrue(managed_object.sensitive) + + def test_set_attribute_with_multivalued_attribute(self): + """ + Test that a KmipError is raised when attempting to set the value of + a multivalued attribute. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + args = ( + payloads.SetAttributeRequestPayload( + unique_identifier="1", + new_attribute=objects.NewAttribute( + attribute=primitives.TextString( + value="New Name", + tag=enums.Tags.NAME + ) + ) + ), + ) + + self.assertRaisesRegex( + exceptions.KmipError, + "The 'Name' attribute is multi-valued. Multi-valued attributes " + "cannot be set with the SetAttribute operation.", + e._process_set_attribute, + *args + ) + + def test_set_attribute_with_non_client_modifiable_attribute(self): + """ + Test that a KmipError is raised when attempting to set the value of + a attribute not modifiable by the client. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + args = ( + payloads.SetAttributeRequestPayload( + unique_identifier="1", + new_attribute=objects.NewAttribute( + attribute=primitives.Enumeration( + enums.CryptographicAlgorithm, + enums.CryptographicAlgorithm.RSA, + enums.Tags.CRYPTOGRAPHIC_ALGORITHM + ) + ) + ), + ) + + self.assertRaisesRegex( + exceptions.KmipError, + "The 'Cryptographic Algorithm' attribute is read-only and cannot " + "be modified by the client.", + e._process_set_attribute, + *args + ) + + def test_modify_attribute(self): + """ + Test that a ModifyAttribute request can be processed correctly. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(1, 4) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + # Confirm that the attribute is set to its default value by + # fetching the managed object fresh from the database and + # checking it. + managed_object = e._get_object_with_access_controls( + "1", + enums.Operation.MODIFY_ATTRIBUTE + ) + self.assertFalse(managed_object.sensitive) + + payload = payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + attribute=attribute_factory.create_attribute( + enums.AttributeType.SENSITIVE, + True + ) + ) + + response_payload = e._process_modify_attribute(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: ModifyAttribute" + ) + self.assertEqual("1", response_payload.unique_identifier) + self.assertEqual( + "Sensitive", + response_payload.attribute.attribute_name.value + ) + self.assertIsNone(response_payload.attribute.attribute_index) + self.assertEqual( + True, + response_payload.attribute.attribute_value.value + ) + + # Confirm that the attribute was actually set by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + response_payload.unique_identifier, + enums.Operation.MODIFY_ATTRIBUTE + ) + self.assertTrue(managed_object.sensitive) + + def test_modify_attribute_with_unmodifiable_attribute(self): + """ + Test that a KmipError is raised when attempting to modify an + unmodifiable attribute. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(1, 4) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + args = ( + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + attribute=attribute_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + "2" + ) + ), + ) + self.assertRaisesRegex( + exceptions.KmipError, + "The 'Unique Identifier' attribute is read-only and cannot be " + "modified.", + e._process_modify_attribute, + *args + ) + + def test_modify_attribute_with_multivalued(self): + """ + Test that a ModifyAttribute request can be processed correctly. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(1, 4) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + # Confirm that the attribute is set to its default value by + # fetching the managed object fresh from the database and + # checking it. + managed_object = e._get_object_with_access_controls( + "1", + enums.Operation.MODIFY_ATTRIBUTE + ) + self.assertEqual("Symmetric Key", managed_object.names[0]) + + payload = payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + attribute=attribute_factory.create_attribute( + enums.AttributeType.NAME, + "Modified Name" + ) + ) + + response_payload = e._process_modify_attribute(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: ModifyAttribute" + ) + self.assertEqual("1", response_payload.unique_identifier) + self.assertEqual( + "Name", + response_payload.attribute.attribute_name.value + ) + self.assertEqual(0, response_payload.attribute.attribute_index.value) + self.assertEqual( + "Modified Name", + response_payload.attribute.attribute_value.name_value.value + ) + + # Confirm that the attribute was actually set by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + response_payload.unique_identifier, + enums.Operation.MODIFY_ATTRIBUTE + ) + self.assertEqual("Modified Name", managed_object.names[0]) + + def test_modify_attribute_with_multivalued_no_index_match(self): + """ + Test that a KmipError is raised when attempting to modify an attribute + based on an index that doesn't exist. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(1, 4) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + args = ( + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + attribute=attribute_factory.create_attribute( + enums.AttributeType.NAME, + "Modified Name", + index=1 + ) + ), + ) + self.assertRaisesRegex( + exceptions.KmipError, + "No matching attribute instance could be found for the specified " + "attribute index.", + e._process_modify_attribute, + *args + ) + + def test_modify_attribute_with_singlevalued_index_specified(self): + """ + Test that a KmipError is raised when attempting to modify a + single-valued attribute while also specifying the attribute index. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(1, 4) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + args = ( + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + attribute=attribute_factory.create_attribute( + enums.AttributeType.SENSITIVE, + True, + index=0 + ) + ), + ) + self.assertRaisesRegex( + exceptions.KmipError, + "The attribute index cannot be specified for a single-valued " + "attribute.", + e._process_modify_attribute, + *args + ) + + def test_modify_attribute_with_singlevalued_unset_attr(self): + """ + Test that a KmipError is raised when attempting to modify an + unset attribute. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(1, 4) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + e._get_attributes_from_managed_object = mock.Mock(return_value=[]) + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + e._set_attribute_on_managed_object( + secret, + ("Sensitive", primitives.Boolean(None)) + ) + + args = ( + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + attribute=attribute_factory.create_attribute( + enums.AttributeType.SENSITIVE, + True + ) + ), + ) + self.assertRaisesRegex( + exceptions.KmipError, + "The 'Sensitive' attribute is not set on the managed " + "object. It must be set before it can be modified.", + e._process_modify_attribute, + *args + ) + + def test_modify_attribute_kmip_2_0(self): + """ + Test that a ModifyAttribute request can be processed correctly with + KMIP 2.0 parameters. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + # Confirm that the attribute is set to its default value by + # fetching the managed object fresh from the database and + # checking it. + managed_object = e._get_object_with_access_controls( + "1", + enums.Operation.MODIFY_ATTRIBUTE + ) + self.assertFalse(managed_object.sensitive) + + payload = payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + current_attribute=objects.CurrentAttribute( + attribute=primitives.Boolean( + False, + tag=enums.Tags.SENSITIVE + ) + ), + new_attribute=objects.NewAttribute( + attribute=primitives.Boolean( + True, + tag=enums.Tags.SENSITIVE + ) + ) + ) + + response_payload = e._process_modify_attribute(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: ModifyAttribute" + ) + self.assertEqual("1", response_payload.unique_identifier) + self.assertIsNone(response_payload.attribute) + + # Confirm that the attribute was actually set by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + response_payload.unique_identifier, + enums.Operation.MODIFY_ATTRIBUTE + ) + self.assertTrue(managed_object.sensitive) + + def test_modify_attribute_kmip_2_0_with_unmodifiable_attribute(self): + """ + Test that a KmipError is raised when attempting to modify an + unmodifiable attribute with KMIP 2.0 parameters. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + args = ( + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + current_attribute=objects.CurrentAttribute( + attribute=primitives.TextString( + "1", + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + ), + new_attribute=objects.NewAttribute( + attribute=primitives.TextString( + "2", + tag=enums.Tags.UNIQUE_IDENTIFIER + ) + ) + ), + ) + self.assertRaisesRegex( + exceptions.KmipError, + "The 'Unique Identifier' attribute is read-only and cannot be " + "modified.", + e._process_modify_attribute, + *args + ) + + def test_modify_attribute_kmip_2_0_with_multivalued(self): + """ + Test that a ModifyAttribute request can be processed correctly with + KMIP 2.0 parameters. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + # Confirm that the attribute is set to its default value by + # fetching the managed object fresh from the database and + # checking it. + managed_object = e._get_object_with_access_controls( + "1", + enums.Operation.MODIFY_ATTRIBUTE + ) + self.assertEqual("Symmetric Key", managed_object.names[0]) + + payload = payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + current_attribute=objects.CurrentAttribute( + attribute=attributes.Name( + name_value=attributes.Name.NameValue("Symmetric Key") + ) + ), + new_attribute=objects.NewAttribute( + attribute=attributes.Name( + name_value=attributes.Name.NameValue("Modified Name") + ) + ) + ) + + response_payload = e._process_modify_attribute(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: ModifyAttribute" + ) + self.assertEqual("1", response_payload.unique_identifier) + self.assertIsNone(response_payload.attribute) + + # Confirm that the attribute was actually set by fetching the + # managed object fresh from the database and checking it. + managed_object = e._get_object_with_access_controls( + response_payload.unique_identifier, + enums.Operation.MODIFY_ATTRIBUTE + ) + self.assertEqual("Modified Name", managed_object.names[0]) + + def test_modify_attribute_kmip_2_0_with_multivalued_no_current(self): + """ + Test that a KmipError is raised when attempting to modifyg a + multivalued attribute with no current attribute. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + args = ( + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + new_attribute=objects.NewAttribute( + attribute=attributes.Name( + name_value=attributes.Name.NameValue("Modified Name") + ) + ) + ), + ) + self.assertRaisesRegex( + exceptions.KmipError, + "The 'Name' attribute is multivalued so the current attribute " + "must be specified.", + e._process_modify_attribute, + *args + ) + + def test_modify_attribute_kmip_2_0_with_multivalued_no_attr_match(self): + """ + Test that a KmipError is raised when attempting to modify an + non-existent attribute value. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + args = ( + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + current_attribute=objects.CurrentAttribute( + attribute=attributes.Name( + name_value=attributes.Name.NameValue("Invalid Key") + ) + ), + new_attribute=objects.NewAttribute( + attribute=attributes.Name( + name_value=attributes.Name.NameValue("Modified Name") + ) + ) + ), + ) + self.assertRaisesRegex( + exceptions.KmipError, + "The specified current attribute could not be found on the " + "managed object.", + e._process_modify_attribute, + *args + ) + + def test_modify_attribute_kmip_2_0_with_singlevalued_unset_attr(self): + """ + Test that a KmipError is raised when attempting to modify an + unset attribute with KMIP 2.0 parameters. + """ + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + e._get_attribute_from_managed_object = mock.Mock(return_value=None) + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._set_attribute_on_managed_object( + secret, + ("Sensitive", primitives.Boolean(None)) + ) + + args = ( + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + new_attribute=objects.NewAttribute( + attribute=primitives.Boolean( + True, + tag=enums.Tags.SENSITIVE + ) + ) + ), + ) + self.assertRaisesRegex( + exceptions.KmipError, + "The 'Sensitive' attribute is not set on the managed " + "object. It must be set before it can be modified.", + e._process_modify_attribute, + *args + ) + + def test_modify_attribute_kmip_2_0_with_singlevalued_no_attr_match(self): + e = engine.KmipEngine() + e._protocol_version = contents.ProtocolVersion(2, 0) + e._attribute_policy._version = e._protocol_version + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + secret = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 0, + b'' + ) + + e._data_session.add(secret) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + args = ( + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + current_attribute=objects.CurrentAttribute( + attribute=primitives.Boolean( + True, + tag=enums.Tags.SENSITIVE + ) + ), + new_attribute=objects.NewAttribute( + attribute=primitives.Boolean( + False, + tag=enums.Tags.SENSITIVE + ) + ) + ), + ) + self.assertRaisesRegex( + exceptions.KmipError, + "The specified current attribute could not be found on the " + "managed object.", + e._process_modify_attribute, + *args + ) + + def test_register(self): + """ + Test that a Register request can be processed correctly. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() + + attribute_factory = factory.AttributeFactory() + + # Build a SymmetricKey for registration. + object_type = enums.ObjectType.SYMMETRIC_KEY + template_attribute = objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + 'Test Symmetric Key', + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 128 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.DECRYPT + ] + ), + attribute_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + 'test' + ) + ] + ) + key_bytes = ( + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + ) + secret = secrets.SymmetricKey( + key_block=objects.KeyBlock( + key_format_type=misc.KeyFormatType(enums.KeyFormatType.RAW), + key_value=objects.KeyValue( + key_material=objects.KeyMaterial(key_bytes) + ), + cryptographic_algorithm=attributes.CryptographicAlgorithm( + enums.CryptographicAlgorithm.AES + ), + cryptographic_length=attributes.CryptographicLength(128) + ) + ) + + payload = payloads.RegisterRequestPayload( + object_type=object_type, + template_attribute=template_attribute, + managed_object=secret + ) + + response_payload = e._process_register(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call( + "Processing operation: Register" + ) + + uid = response_payload.unique_identifier + self.assertEqual('1', uid) + + # Retrieve the stored object and verify all attributes were set + # appropriately. + symmetric_key = e._data_session.query( + pie_objects.SymmetricKey + ).filter( + pie_objects.ManagedObject.unique_identifier == uid + ).one() + self.assertEqual( + enums.KeyFormatType.RAW, + symmetric_key.key_format_type + ) + self.assertEqual(1, len(symmetric_key.names)) + self.assertIn('Test Symmetric Key', symmetric_key.names) + self.assertEqual(key_bytes, symmetric_key.value) + self.assertEqual( + enums.CryptographicAlgorithm.AES, + symmetric_key.cryptographic_algorithm + ) + self.assertEqual(128, symmetric_key.cryptographic_length) + self.assertEqual(2, len(symmetric_key.cryptographic_usage_masks)) + self.assertIn( + enums.CryptographicUsageMask.ENCRYPT, + symmetric_key.cryptographic_usage_masks + ) + self.assertIn( + enums.CryptographicUsageMask.DECRYPT, + symmetric_key.cryptographic_usage_masks + ) + self.assertEqual('test', symmetric_key.operation_policy_name) + self.assertIsNotNone(symmetric_key.initial_date) + self.assertNotEqual(0, symmetric_key.initial_date) + + self.assertEqual(uid, e._id_placeholder) + + def test_register_unsupported_object_type(self): + """ + Test that an InvalidField error is generated when attempting to + register an unsupported object type. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() + + object_type = enums.ObjectType.TEMPLATE + payload = payloads.RegisterRequestPayload(object_type=object_type) + + args = (payload, ) + regex = "The Template object type is not supported." + six.assertRaisesRegex( + self, + exceptions.InvalidField, + regex, + e._process_register, + *args + ) + + def test_request_omitting_secret(self): + """ + Test that an InvalidField error is generate when trying to register + a secret in absentia. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() + + object_type = enums.ObjectType.SYMMETRIC_KEY + payload = payloads.RegisterRequestPayload(object_type=object_type) + + args = (payload, ) + regex = "Cannot register a secret in absentia." + six.assertRaisesRegex( + self, + exceptions.InvalidField, + regex, + e._process_register, + *args + ) + + def test_derive_key(self): + """ + Test that a DeriveKey request can be processed correctly. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + e._cryptography_engine.logger = mock.MagicMock() + + base_key = pie_objects.SymmetricKey( + algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, + length=176, + value=( + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b\x0b\x0b' + ), + masks=[enums.CryptographicUsageMask.DERIVE_KEY] + ) + e._data_session.add(base_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + # Derive a SymmetricKey object. + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.SYMMETRIC_KEY, + unique_identifiers=[str(base_key.unique_identifier)], + derivation_method=enums.DerivationMethod.HMAC, + derivation_parameters=attributes.DerivationParameters( + cryptographic_parameters=attributes.CryptographicParameters( + hashing_algorithm=enums.HashingAlgorithm.SHA_256 + ), + derivation_data=( + b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7' + b'\xf8\xf9' + ), + salt=( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0a\x0b\x0c' + ) + ), + template_attribute=objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 336 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ) + ] + ) + ) + + response_payload = e._process_derive_key(payload) + + e._logger.info.assert_any_call("Processing operation: DeriveKey") + e._logger.info.assert_any_call( + "Object 1 will be used as the keying material for the derivation " + "process." + ) + e._logger.info.assert_any_call("Created a SymmetricKey with ID: 2") + + self.assertEqual("2", response_payload.unique_identifier) + + managed_object = e._data_session.query( + pie_objects.SymmetricKey + ).filter( + pie_objects.SymmetricKey.unique_identifier == 2 + ).one() + + self.assertEqual( + ( + b'\x3c\xb2\x5f\x25\xfa\xac\xd5\x7a' + b'\x90\x43\x4f\x64\xd0\x36\x2f\x2a' + b'\x2d\x2d\x0a\x90\xcf\x1a\x5a\x4c' + b'\x5d\xb0\x2d\x56\xec\xc4\xc5\xbf' + b'\x34\x00\x72\x08\xd5\xb8\x87\x18' + b'\x58\x65' + ), + managed_object.value + ) + self.assertEqual( + enums.CryptographicAlgorithm.AES, + managed_object.cryptographic_algorithm + ) + self.assertEqual( + 336, + managed_object.cryptographic_length + ) + self.assertIsNotNone(managed_object.initial_date) + + e._logger.reset_mock() + + base_key = pie_objects.SymmetricKey( + algorithm=enums.CryptographicAlgorithm.BLOWFISH, + length=128, + value=( + b'\x01\x23\x45\x67\x89\xAB\xCD\xEF' + b'\xF0\xE1\xD2\xC3\xB4\xA5\x96\x87' + ), + masks=[enums.CryptographicUsageMask.DERIVE_KEY] + ) + e._data_session.add(base_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + # Derive a SecretData object. + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.SECRET_DATA, + unique_identifiers=[str(base_key.unique_identifier)], + derivation_method=enums.DerivationMethod.ENCRYPT, + derivation_parameters=attributes.DerivationParameters( + cryptographic_parameters=attributes.CryptographicParameters( + block_cipher_mode=enums.BlockCipherMode.CBC, + padding_method=enums.PaddingMethod.PKCS5, + hashing_algorithm=enums.HashingAlgorithm.SHA_256, + cryptographic_algorithm=( + enums.CryptographicAlgorithm.BLOWFISH + ) + ), + initialization_vector=b'\xFE\xDC\xBA\x98\x76\x54\x32\x10', + derivation_data=( + b'\x37\x36\x35\x34\x33\x32\x31\x20' + b'\x4E\x6F\x77\x20\x69\x73\x20\x74' + b'\x68\x65\x20\x74\x69\x6D\x65\x20' + b'\x66\x6F\x72\x20\x00' + ), + ), + template_attribute=objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 256 + ) + ] + ) + ) + + response_payload = e._process_derive_key(payload) + + e._logger.info.assert_any_call("Processing operation: DeriveKey") + e._logger.info.assert_any_call( + "Object 3 will be used as the keying material for the derivation " + "process." + ) + e._logger.info.assert_any_call("Created a SecretData with ID: 4") + + self.assertEqual("4", response_payload.unique_identifier) + + managed_object = e._data_session.query( + pie_objects.SecretData + ).filter( + pie_objects.SecretData.unique_identifier == 4 + ).one() + + self.assertEqual( + ( + b'\x6B\x77\xB4\xD6\x30\x06\xDE\xE6' + b'\x05\xB1\x56\xE2\x74\x03\x97\x93' + b'\x58\xDE\xB9\xE7\x15\x46\x16\xD9' + b'\x74\x9D\xEC\xBE\xC0\x5D\x26\x4B' + ), + managed_object.value + ) + self.assertEqual(enums.SecretDataType.SEED, managed_object.data_type) + self.assertIsNotNone(managed_object.initial_date) + + def test_derive_key_truncation(self): + """ + Test that a derived key is properly truncated after it is generated if + needed. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + e._cryptography_engine.logger = mock.MagicMock() + + base_key = pie_objects.SymmetricKey( + algorithm=enums.CryptographicAlgorithm.BLOWFISH, + length=128, + value=( + b'\x01\x23\x45\x67\x89\xAB\xCD\xEF' + b'\xF0\xE1\xD2\xC3\xB4\xA5\x96\x87' + ), + masks=[enums.CryptographicUsageMask.DERIVE_KEY] + ) + e._data_session.add(base_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + # Derive a SymmetricKey object. + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.SYMMETRIC_KEY, + unique_identifiers=[str(base_key.unique_identifier)], + derivation_method=enums.DerivationMethod.ENCRYPT, + derivation_parameters=attributes.DerivationParameters( + cryptographic_parameters=attributes.CryptographicParameters( + block_cipher_mode=enums.BlockCipherMode.CBC, + padding_method=enums.PaddingMethod.PKCS5, + cryptographic_algorithm=enums.CryptographicAlgorithm. + BLOWFISH + ), + derivation_data=( + b'\x37\x36\x35\x34\x33\x32\x31\x20' + b'\x4E\x6F\x77\x20\x69\x73\x20\x74' + b'\x68\x65\x20\x74\x69\x6D\x65\x20' + b'\x66\x6F\x72\x20\x00' + ), + initialization_vector=b'\xFE\xDC\xBA\x98\x76\x54\x32\x10' + ), + template_attribute=objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 128 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ) + ] + ) + ) + + response_payload = e._process_derive_key(payload) + + e._logger.info.assert_any_call("Processing operation: DeriveKey") + e._logger.info.assert_any_call( + "Object 1 will be used as the keying material for the derivation " + "process." ) - e._logger.reset_mock() + e._logger.info.assert_any_call("Created a SymmetricKey with ID: 2") - # Test that a missing PrivateKey CryptographicUsageMask raises an error - common_template = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.NAME, - attributes.Name.create( - 'Test Asymmetric Key', - enums.NameType.UNINTERPRETED_TEXT_STRING - ) - ) - ], - tag=enums.Tags.COMMON_TEMPLATE_ATTRIBUTE - ) - public_template = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.RSA - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 2048 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, - [ - enums.CryptographicUsageMask.ENCRYPT - ] - ) - ], - tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE + self.assertEqual("2", response_payload.unique_identifier) + + managed_object = e._data_session.query( + pie_objects.SymmetricKey + ).filter( + pie_objects.SymmetricKey.unique_identifier == 2 + ).one() + + self.assertEqual( + ( + b'\x6B\x77\xB4\xD6\x30\x06\xDE\xE6' + b'\x05\xB1\x56\xE2\x74\x03\x97\x93' + ), + managed_object.value ) - private_template = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.RSA - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 2048 - ) - ], - tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE + self.assertEqual( + enums.CryptographicAlgorithm.AES, + managed_object.cryptographic_algorithm ) - payload = payloads.CreateKeyPairRequestPayload( - common_template, - private_template, - public_template + self.assertEqual(128, managed_object.cryptographic_length) + self.assertIsNotNone(managed_object.initial_date) + + def test_derive_key_invalid_derivation_type(self): + """ + Test that the right error is thrown when an invalid derivation type + is provided with a DeriveKey request. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._logger = mock.MagicMock() + e._cryptography_engine.logger = mock.MagicMock() + + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.CERTIFICATE ) args = (payload, ) - regex = ( - "The cryptographic usage mask must be specified as an attribute " - "for the private key." - ) - six.assertRaisesRegex( - self, + self.assertRaisesRegex( exceptions.InvalidField, - regex, - e._process_create_key_pair, + "Key derivation can only generate a SymmetricKey or SecretData " + "object.", + e._process_derive_key, *args ) - e._logger.info.assert_any_call( - "Processing operation: CreateKeyPair" - ) - e._logger.reset_mock() - def test_create_key_pair_mismatched_attributes(self): + def test_derive_key_invalid_base_key(self): """ - Test that the right errors are generated when required attributes - are mismatched in a CreateKeyPair request. + Test that the right error is thrown when an object not suitable for + key derivation is provided as the base key with a DeriveKey request. """ e = engine.KmipEngine() e._data_store = self.engine e._data_store_session_factory = self.session_factory e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() + e._cryptography_engine.logger = mock.MagicMock() - attribute_factory = factory.AttributeFactory() - - # Test that mismatched CryptographicAlgorithms raise an error. - common_template = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.NAME, - attributes.Name.create( - 'Test Asymmetric Key', - enums.NameType.UNINTERPRETED_TEXT_STRING - ) - ) - ], - tag=enums.Tags.COMMON_TEMPLATE_ATTRIBUTE - ) - public_template = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.RSA - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 2048 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, - [ - enums.CryptographicUsageMask.ENCRYPT - ] - ) - ], - tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE - ) - private_template = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.DSA - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 2048 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, - [ - enums.CryptographicUsageMask.DECRYPT - ] - ) - ], - tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE + invalid_key = pie_objects.OpaqueObject( + b'\x01\x02\x04\x08\x10\x20\x40\x80', + enums.OpaqueDataType.NONE ) - payload = payloads.CreateKeyPairRequestPayload( - common_template, - private_template, - public_template + e._data_session.add(invalid_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.SECRET_DATA, + unique_identifiers=[str(invalid_key.unique_identifier)] ) args = (payload, ) - regex = ( - "The public and private key algorithms must be the same." - ) - six.assertRaisesRegex( - self, + self.assertRaisesRegex( exceptions.InvalidField, - regex, - e._process_create_key_pair, + "Object 1 is not a suitable type for key derivation. Please " + "specify a key or secret data.", + e._process_derive_key, *args ) - e._logger.info.assert_any_call( - "Processing operation: CreateKeyPair" - ) - e._logger.reset_mock() - # Test that mismatched CryptographicAlgorithms raise an error. - common_template = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.NAME, - attributes.Name.create( - 'Test Asymmetric Key', - enums.NameType.UNINTERPRETED_TEXT_STRING - ) - ) - ], - tag=enums.Tags.COMMON_TEMPLATE_ATTRIBUTE - ) - public_template = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.RSA - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 2048 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, - [ - enums.CryptographicUsageMask.ENCRYPT - ] - ) - ], - tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE - ) - private_template = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.RSA - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 4096 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, - [ - enums.CryptographicUsageMask.DECRYPT - ] - ) - ], - tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE + def test_derive_key_non_derivable_base_key(self): + """ + Test that the right error is thrown when an object suitable for + key derivation but not marked as such is provided as the base key + with a DeriveKey request. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + e._cryptography_engine.logger = mock.MagicMock() + + base_key = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + ( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + ), + [enums.CryptographicUsageMask.ENCRYPT] ) - payload = payloads.CreateKeyPairRequestPayload( - common_template, - private_template, - public_template + e._data_session.add(base_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.SECRET_DATA, + unique_identifiers=[str(base_key.unique_identifier)] ) args = (payload, ) - regex = ( - "The public and private key lengths must be the same." - ) - six.assertRaisesRegex( - self, + self.assertRaisesRegex( exceptions.InvalidField, - regex, - e._process_create_key_pair, + "The DeriveKey bit must be set in the cryptographic usage mask " + "for object 1 for it to be used in key derivation.", + e._process_derive_key, *args ) - e._logger.info.assert_any_call( - "Processing operation: CreateKeyPair" - ) - e._logger.reset_mock() - def test_register(self): + def test_derive_key_alternate_derivation_data(self): """ - Test that a Register request can be processed correctly. + Test that a DeriveKey request can be processed correctly by + specifying multiple base objects and no derivation data. """ e = engine.KmipEngine() e._data_store = self.engine e._data_store_session_factory = self.session_factory e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() + e._cryptography_engine.logger = mock.MagicMock() + + base_key = pie_objects.SymmetricKey( + algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, + length=176, + value=( + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b\x0b\x0b' + ), + masks=[enums.CryptographicUsageMask.DERIVE_KEY] + ) + e._data_session.add(base_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + base_data = pie_objects.SecretData( + value=( + b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7' + b'\xf8\xf9' + ), + data_type=enums.SecretDataType.SEED, + masks=[enums.CryptographicUsageMask.DERIVE_KEY] + ) + e._data_session.add(base_data) + e._data_session.commit() + e._data_session = e._data_store_session_factory() attribute_factory = factory.AttributeFactory() - # Build a SymmetricKey for registration. - object_type = enums.ObjectType.SYMMETRIC_KEY - template_attribute = objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.NAME, - attributes.Name.create( - 'Test Symmetric Key', - enums.NameType.UNINTERPRETED_TEXT_STRING - ) - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.AES - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 128 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, - [ - enums.CryptographicUsageMask.ENCRYPT, - enums.CryptographicUsageMask.DECRYPT - ] + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.SYMMETRIC_KEY, + unique_identifiers=[ + str(base_key.unique_identifier), + str(base_data.unique_identifier) + ], + derivation_method=enums.DerivationMethod.HMAC, + derivation_parameters=attributes.DerivationParameters( + cryptographic_parameters=attributes.CryptographicParameters( + hashing_algorithm=enums.HashingAlgorithm.SHA_256 ), - attribute_factory.create_attribute( - enums.AttributeType.OPERATION_POLICY_NAME, - 'test' + salt=( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0a\x0b\x0c' ) - ] - ) - key_bytes = ( - b'\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00' - ) - secret = secrets.SymmetricKey( - key_block=objects.KeyBlock( - key_format_type=misc.KeyFormatType(enums.KeyFormatType.RAW), - key_value=objects.KeyValue( - key_material=objects.KeyMaterial(key_bytes) - ), - cryptographic_algorithm=attributes.CryptographicAlgorithm( - enums.CryptographicAlgorithm.AES - ), - cryptographic_length=attributes.CryptographicLength(128) + ), + template_attribute=objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 336 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ) + ] ) ) - payload = payloads.RegisterRequestPayload( - object_type=object_type, - template_attribute=template_attribute, - managed_object=secret - ) - - response_payload = e._process_register(payload) - e._data_session.commit() - e._data_session = e._data_store_session_factory() + response_payload = e._process_derive_key(payload) + e._logger.info.assert_any_call("Processing operation: DeriveKey") e._logger.info.assert_any_call( - "Processing operation: Register" + "2 derivation objects specified with the DeriveKey request." + ) + e._logger.info.assert_any_call( + "Object 1 will be used as the keying material for the derivation " + "process." + ) + e._logger.info.assert_any_call( + "Object 2 will be used as the derivation data for the derivation " + "process." ) + e._logger.info.assert_any_call("Created a SymmetricKey with ID: 3") - uid = response_payload.unique_identifier - self.assertEqual('1', uid) + self.assertEqual("3", response_payload.unique_identifier) - # Retrieve the stored object and verify all attributes were set - # appropriately. - symmetric_key = e._data_session.query( + managed_object = e._data_session.query( pie_objects.SymmetricKey ).filter( - pie_objects.ManagedObject.unique_identifier == uid + pie_objects.SymmetricKey.unique_identifier == 3 ).one() + self.assertEqual( - enums.KeyFormatType.RAW, - symmetric_key.key_format_type + ( + b'\x3c\xb2\x5f\x25\xfa\xac\xd5\x7a' + b'\x90\x43\x4f\x64\xd0\x36\x2f\x2a' + b'\x2d\x2d\x0a\x90\xcf\x1a\x5a\x4c' + b'\x5d\xb0\x2d\x56\xec\xc4\xc5\xbf' + b'\x34\x00\x72\x08\xd5\xb8\x87\x18' + b'\x58\x65' + ), + managed_object.value ) - self.assertEqual(1, len(symmetric_key.names)) - self.assertIn('Test Symmetric Key', symmetric_key.names) - self.assertEqual(key_bytes, symmetric_key.value) self.assertEqual( enums.CryptographicAlgorithm.AES, - symmetric_key.cryptographic_algorithm + managed_object.cryptographic_algorithm ) - self.assertEqual(128, symmetric_key.cryptographic_length) - self.assertEqual(2, len(symmetric_key.cryptographic_usage_masks)) - self.assertIn( - enums.CryptographicUsageMask.ENCRYPT, - symmetric_key.cryptographic_usage_masks + self.assertEqual( + 336, + managed_object.cryptographic_length ) - self.assertIn( - enums.CryptographicUsageMask.DECRYPT, - symmetric_key.cryptographic_usage_masks + self.assertIsNotNone(managed_object.initial_date) + + def test_derive_key_unspecified_iv(self): + """ + """ + self.skipTest('') + + def test_derive_key_missing_cryptographic_length(self): + """ + Test that the right error is thrown when the cryptographic length is + missing from a DeriveKey request. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + e._cryptography_engine.logger = mock.MagicMock() + + base_key = pie_objects.SymmetricKey( + algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, + length=160, + value=( + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b' + ), + masks=[enums.CryptographicUsageMask.DERIVE_KEY] + ) + e._data_session.add(base_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.SYMMETRIC_KEY, + unique_identifiers=[str(base_key.unique_identifier)], + derivation_method=enums.DerivationMethod.HMAC, + derivation_parameters=attributes.DerivationParameters( + cryptographic_parameters=attributes.CryptographicParameters( + hashing_algorithm=enums.HashingAlgorithm.SHA_256 + ), + derivation_data=b'\x48\x69\x20\x54\x68\x65\x72\x65', + ), + template_attribute=objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ) + ] + ) ) - self.assertEqual('test', symmetric_key.operation_policy_name) - self.assertIsNotNone(symmetric_key.initial_date) - self.assertNotEqual(0, symmetric_key.initial_date) - self.assertEqual(uid, e._id_placeholder) + args = (payload, ) + self.assertRaisesRegex( + exceptions.InvalidField, + "The cryptographic length must be provided in the template " + "attribute.", + e._process_derive_key, + *args + ) - def test_register_unsupported_object_type(self): + def test_derive_key_invalid_cryptographic_length(self): """ - Test that an InvalidField error is generated when attempting to - register an unsupported object type. + Test that the right error is thrown when an invalid cryptographic + length is provided with a DeriveKey request. """ e = engine.KmipEngine() e._data_store = self.engine e._data_store_session_factory = self.session_factory e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() + e._cryptography_engine.logger = mock.MagicMock() - object_type = enums.ObjectType.SPLIT_KEY - payload = payloads.RegisterRequestPayload(object_type=object_type) + base_key = pie_objects.SymmetricKey( + algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, + length=160, + value=( + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b' + ), + masks=[enums.CryptographicUsageMask.DERIVE_KEY] + ) + e._data_session.add(base_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.SYMMETRIC_KEY, + unique_identifiers=[str(base_key.unique_identifier)], + derivation_method=enums.DerivationMethod.HMAC, + derivation_parameters=attributes.DerivationParameters( + cryptographic_parameters=attributes.CryptographicParameters( + hashing_algorithm=enums.HashingAlgorithm.SHA_256 + ), + derivation_data=b'\x48\x69\x20\x54\x68\x65\x72\x65', + ), + template_attribute=objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 123 + ), + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ) + ] + ) + ) args = (payload, ) - regex = "The SplitKey object type is not supported." - six.assertRaisesRegex( - self, + self.assertRaisesRegex( exceptions.InvalidField, - regex, - e._process_register, + "The cryptographic length must correspond to a valid number of " + "bytes; it must be a multiple of 8.", + e._process_derive_key, *args ) - def test_request_omitting_secret(self): + def test_derive_key_missing_cryptographic_algorithm(self): """ - Test that an InvalidField error is generate when trying to register - a secret in absentia. + Test that the right error is thrown when the cryptographic algorithm + is missing from a DeriveKey request when deriving a symmetric key. """ e = engine.KmipEngine() e._data_store = self.engine e._data_store_session_factory = self.session_factory e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() + e._cryptography_engine.logger = mock.MagicMock() - object_type = enums.ObjectType.SYMMETRIC_KEY - payload = payloads.RegisterRequestPayload(object_type=object_type) + base_key = pie_objects.SymmetricKey( + algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, + length=160, + value=( + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b' + ), + masks=[enums.CryptographicUsageMask.DERIVE_KEY] + ) + e._data_session.add(base_key) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + attribute_factory = factory.AttributeFactory() + + payload = payloads.DeriveKeyRequestPayload( + object_type=enums.ObjectType.SYMMETRIC_KEY, + unique_identifiers=[str(base_key.unique_identifier)], + derivation_method=enums.DerivationMethod.HMAC, + derivation_parameters=attributes.DerivationParameters( + cryptographic_parameters=attributes.CryptographicParameters( + hashing_algorithm=enums.HashingAlgorithm.SHA_256 + ), + derivation_data=b'\x48\x69\x20\x54\x68\x65\x72\x65', + ), + template_attribute=objects.TemplateAttribute( + attributes=[ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 256 + ) + ] + ) + ) args = (payload, ) - regex = "Cannot register a secret in absentia." - six.assertRaisesRegex( - self, + self.assertRaisesRegex( exceptions.InvalidField, - regex, - e._process_register, + "The cryptographic algorithm must be provided in the template " + "attribute when deriving a symmetric key.", + e._process_derive_key, *args ) - def test_derive_key(self): + def test_derive_key_oversized_cryptographic_length(self): """ - Test that a DeriveKey request can be processed correctly. + Test that the right error is thrown when an invalid cryptographic + length is provided with a DeriveKey request. """ e = engine.KmipEngine() e._data_store = self.engine @@ -3545,11 +6286,11 @@ base_key = pie_objects.SymmetricKey( algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, - length=176, + length=160, value=( b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b\x0b\x0b' + b'\x0b\x0b\x0b\x0b' ), masks=[enums.CryptographicUsageMask.DERIVE_KEY] ) @@ -3557,9 +6298,11 @@ e._data_session.commit() e._data_session = e._data_store_session_factory() + e._cryptography_engine = mock.MagicMock() + e._cryptography_engine.derive_key.return_value = b'' + attribute_factory = factory.AttributeFactory() - # Derive a SymmetricKey object. payload = payloads.DeriveKeyRequestPayload( object_type=enums.ObjectType.SYMMETRIC_KEY, unique_identifiers=[str(base_key.unique_identifier)], @@ -3568,20 +6311,13 @@ cryptographic_parameters=attributes.CryptographicParameters( hashing_algorithm=enums.HashingAlgorithm.SHA_256 ), - derivation_data=( - b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7' - b'\xf8\xf9' - ), - salt=( - b'\x00\x01\x02\x03\x04\x05\x06\x07' - b'\x08\x09\x0a\x0b\x0c' - ) + derivation_data=b'\x48\x69\x20\x54\x68\x65\x72\x65', ), template_attribute=objects.TemplateAttribute( attributes=[ attribute_factory.create_attribute( enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 336 + 256 ), attribute_factory.create_attribute( enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, @@ -3591,124 +6327,327 @@ ) ) - response_payload = e._process_derive_key(payload) + args = (payload, ) + self.assertRaisesRegex( + exceptions.CryptographicFailure, + "The specified length exceeds the output of the derivation " + "method.", + e._process_derive_key, + *args + ) + + def test_is_valid_date(self): + """ + Test that object date checking yields the correct results. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + # If the date range isn't fully defined, the value is implicitly valid. + self.assertTrue( + e._is_valid_date( + enums.AttributeType.INITIAL_DATE, + 1563564520, + None, + None + ) + ) + self.assertTrue( + e._is_valid_date( + enums.AttributeType.INITIAL_DATE, + 1563564520, + None, + 1563564521 + ) + ) + + # Verify the value is valid for a fully defined, encompassing range. + self.assertTrue( + e._is_valid_date( + enums.AttributeType.INITIAL_DATE, + 1563564520, + 1563564519, + 1563564521 + ) + ) + + # Verify the value is valid for a specific date value. + self.assertTrue( + e._is_valid_date( + enums.AttributeType.INITIAL_DATE, + 1563564520, + 1563564520, + None + ) + ) + + # Verify the value is invalid for a specific date value. + self.assertFalse( + e._is_valid_date( + enums.AttributeType.INITIAL_DATE, + 1563564520, + 1563564519, + None + ) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "object's initial date (Fri Jul 19 19:28:40 2019) does not match " + "the specified initial date (Fri Jul 19 19:28:39 2019)." + ) + e._logger.reset_mock() + + # Verify the value is invalid below a specific date range. + self.assertFalse( + e._is_valid_date( + enums.AttributeType.INITIAL_DATE, + 1563564519, + 1563564520, + 1563564521 + ) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "object's initial date (Fri Jul 19 19:28:39 2019) is less than " + "the starting initial date (Fri Jul 19 19:28:40 2019)." + ) + e._logger.reset_mock() + + # Verify the value is invalid above a specific date range. + self.assertFalse( + e._is_valid_date( + enums.AttributeType.INITIAL_DATE, + 1563564521, + 1563564519, + 1563564520 + ) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "object's initial date (Fri Jul 19 19:28:41 2019) is greater than " + "the ending initial date (Fri Jul 19 19:28:40 2019)." + ) + + def test_track_date_attributes(self): + """ + Test date attribute value tracking with a simple dictionary. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + date_values = {} + + # Verify the first date given is considered the starting range value. + e._track_date_attributes( + enums.AttributeType.INITIAL_DATE, + date_values, + 1563564519 + ) + self.assertEqual(1563564519, date_values["start"]) + + # Verify the second date given is considered the ending range value. + e._track_date_attributes( + enums.AttributeType.INITIAL_DATE, + date_values, + 1563564521 + ) + self.assertEqual(1563564519, date_values["start"]) + self.assertEqual(1563564521, date_values["end"]) + + # Verify that the third date given triggers an exception. + args = (enums.AttributeType.INITIAL_DATE, date_values, 1563564520) + six.assertRaisesRegex( + self, + exceptions.InvalidField, + "Too many Initial Date attributes provided. " + "Include one for an exact match. " + "Include two for a ranged match.", + e._track_date_attributes, + *args + ) + + # Verify that a lower second date is interpreted as the new start date. + date_values = {} + date_values["start"] = 1563564521 + e._track_date_attributes( + enums.AttributeType.INITIAL_DATE, + date_values, + 1563564519 + ) + self.assertEqual(1563564519, date_values["start"]) + self.assertEqual(1563564521, date_values["end"]) + + def test_locate(self): + """ + Test that a Locate request can be processed correctly. + """ + # TODO Need add more extensive tests after locate operaton is + # fully supported + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + obj_a = pie_objects.OpaqueObject(b'', enums.OpaqueDataType.NONE) + obj_b = pie_objects.OpaqueObject(b'', enums.OpaqueDataType.NONE) + + # locate should return nothing at beginning + payload = payloads.LocateRequestPayload() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + self.assertEqual(len(response_payload.unique_identifiers), 0) + + # Add the first obj and test the locate + e._data_session.add(obj_a) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + id_a = str(obj_a.unique_identifier) + + payload = payloads.LocateRequestPayload() + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + + self.assertEqual(len(response_payload.unique_identifiers), 1) + self.assertEqual(id_a, response_payload.unique_identifiers[0]) + + # Add the second obj and test the locate + e._data_session.add(obj_b) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + id_b = str(obj_b.unique_identifier) + + payload = payloads.LocateRequestPayload() + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - e._logger.info.assert_any_call("Processing operation: DeriveKey") - e._logger.info.assert_any_call( - "Object 1 will be used as the keying material for the derivation " - "process." - ) - e._logger.info.assert_any_call("Created a SymmetricKey with ID: 2") + e._logger.info.assert_any_call("Processing operation: Locate") - self.assertEqual("2", response_payload.unique_identifier) + self.assertEqual(len(response_payload.unique_identifiers), 2) + self.assertIn(id_a, response_payload.unique_identifiers) + self.assertIn(id_b, response_payload.unique_identifiers) - managed_object = e._data_session.query( - pie_objects.SymmetricKey - ).filter( - pie_objects.SymmetricKey.unique_identifier == 2 - ).one() + def test_locate_with_offset_and_maximum_items(self): + """ + Test locate operation with specified offset and maximum item limits. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() - self.assertEqual( - ( - b'\x3c\xb2\x5f\x25\xfa\xac\xd5\x7a' - b'\x90\x43\x4f\x64\xd0\x36\x2f\x2a' - b'\x2d\x2d\x0a\x90\xcf\x1a\x5a\x4c' - b'\x5d\xb0\x2d\x56\xec\xc4\xc5\xbf' - b'\x34\x00\x72\x08\xd5\xb8\x87\x18' - b'\x58\x65' - ), - managed_object.value + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - self.assertEqual( + obj_a = pie_objects.SymmetricKey( enums.CryptographicAlgorithm.AES, - managed_object.cryptographic_algorithm + 128, + key, + name='name1' ) - self.assertEqual( - 336, - managed_object.cryptographic_length + obj_a.initial_date = int(time.time()) + time.sleep(2) + obj_b = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.DES, + 128, + key, + name='name2' ) - self.assertIsNotNone(managed_object.initial_date) + obj_b.initial_date = int(time.time()) + time.sleep(2) + obj_c = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name3' + ) + obj_c.initial_date = int(time.time()) + + e._data_session.add(obj_a) + e._data_session.add(obj_b) + e._data_session.add(obj_c) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + id_a = str(obj_a.unique_identifier) + id_b = str(obj_b.unique_identifier) + id_c = str(obj_c.unique_identifier) + + # Locate all objects. + payload = payloads.LocateRequestPayload() e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - base_key = pie_objects.SymmetricKey( - algorithm=enums.CryptographicAlgorithm.BLOWFISH, - length=128, - value=( - b'\x01\x23\x45\x67\x89\xAB\xCD\xEF' - b'\xF0\xE1\xD2\xC3\xB4\xA5\x96\x87' - ), - masks=[enums.CryptographicUsageMask.DERIVE_KEY] + e._logger.info.assert_any_call("Processing operation: Locate") + + self.assertEqual( + [id_c, id_b, id_a], + response_payload.unique_identifiers ) - e._data_session.add(base_key) + + # Locate by skipping the first object and only returning one object. + payload = payloads.LocateRequestPayload( + offset_items=1, + maximum_items=1 + ) + e._logger.reset_mock() + response_payload = e._process_locate(payload) e._data_session.commit() e._data_session = e._data_store_session_factory() - # Derive a SecretData object. - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.SECRET_DATA, - unique_identifiers=[str(base_key.unique_identifier)], - derivation_method=enums.DerivationMethod.ENCRYPT, - derivation_parameters=attributes.DerivationParameters( - cryptographic_parameters=attributes.CryptographicParameters( - block_cipher_mode=enums.BlockCipherMode.CBC, - padding_method=enums.PaddingMethod.PKCS5, - hashing_algorithm=enums.HashingAlgorithm.SHA_256, - cryptographic_algorithm=( - enums.CryptographicAlgorithm.BLOWFISH - ) - ), - initialization_vector=b'\xFE\xDC\xBA\x98\x76\x54\x32\x10', - derivation_data=( - b'\x37\x36\x35\x34\x33\x32\x31\x20' - b'\x4E\x6F\x77\x20\x69\x73\x20\x74' - b'\x68\x65\x20\x74\x69\x6D\x65\x20' - b'\x66\x6F\x72\x20\x00' - ), - ), - template_attribute=objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 256 - ) - ] - ) - ) + e._logger.info.assert_any_call("Processing operation: Locate") - response_payload = e._process_derive_key(payload) + self.assertEqual([id_b], response_payload.unique_identifiers) - e._logger.info.assert_any_call("Processing operation: DeriveKey") - e._logger.info.assert_any_call( - "Object 3 will be used as the keying material for the derivation " - "process." - ) - e._logger.info.assert_any_call("Created a SecretData with ID: 4") + # Locate by skipping the first two objects. + payload = payloads.LocateRequestPayload(offset_items=2) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - self.assertEqual("4", response_payload.unique_identifier) + e._logger.info.assert_any_call("Processing operation: Locate") - managed_object = e._data_session.query( - pie_objects.SecretData - ).filter( - pie_objects.SecretData.unique_identifier == 4 - ).one() + self.assertEqual([id_a], response_payload.unique_identifiers) - self.assertEqual( - ( - b'\x6B\x77\xB4\xD6\x30\x06\xDE\xE6' - b'\x05\xB1\x56\xE2\x74\x03\x97\x93' - b'\x58\xDE\xB9\xE7\x15\x46\x16\xD9' - b'\x74\x9D\xEC\xBE\xC0\x5D\x26\x4B' - ), - managed_object.value - ) - self.assertEqual(enums.SecretDataType.SEED, managed_object.data_type) - self.assertIsNotNone(managed_object.initial_date) + # Locate by only returning two objects. + payload = payloads.LocateRequestPayload(maximum_items=2) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - def test_derive_key_truncation(self): + e._logger.info.assert_any_call("Processing operation: Locate") + + self.assertEqual([id_c, id_b], response_payload.unique_identifiers) + + def test_locate_with_name(self): """ - Test that a derived key is properly truncated after it is generated if - needed. + Test locate operation when 'Name' attribute is given. """ e = engine.KmipEngine() e._data_store = self.engine @@ -3716,153 +6655,207 @@ e._data_session = e._data_store_session_factory() e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - e._cryptography_engine.logger = mock.MagicMock() - base_key = pie_objects.SymmetricKey( - algorithm=enums.CryptographicAlgorithm.BLOWFISH, - length=128, - value=( - b'\x01\x23\x45\x67\x89\xAB\xCD\xEF' - b'\xF0\xE1\xD2\xC3\xB4\xA5\x96\x87' - ), - masks=[enums.CryptographicUsageMask.DERIVE_KEY] + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - e._data_session.add(base_key) + obj_a = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name0' + ) + obj_b = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.DES, + 128, + key, + name='name0' + ) + obj_c = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name1' + ) + + e._data_session.add(obj_a) + e._data_session.add(obj_b) + e._data_session.add(obj_c) e._data_session.commit() e._data_session = e._data_store_session_factory() + id_a = str(obj_a.unique_identifier) + id_b = str(obj_b.unique_identifier) + id_c = str(obj_c.unique_identifier) + attribute_factory = factory.AttributeFactory() - # Derive a SymmetricKey object. - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.SYMMETRIC_KEY, - unique_identifiers=[str(base_key.unique_identifier)], - derivation_method=enums.DerivationMethod.ENCRYPT, - derivation_parameters=attributes.DerivationParameters( - cryptographic_parameters=attributes.CryptographicParameters( - block_cipher_mode=enums.BlockCipherMode.CBC, - padding_method=enums.PaddingMethod.PKCS5, - cryptographic_algorithm=enums.CryptographicAlgorithm. - BLOWFISH - ), - derivation_data=( - b'\x37\x36\x35\x34\x33\x32\x31\x20' - b'\x4E\x6F\x77\x20\x69\x73\x20\x74' - b'\x68\x65\x20\x74\x69\x6D\x65\x20' - b'\x66\x6F\x72\x20\x00' - ), - initialization_vector=b'\xFE\xDC\xBA\x98\x76\x54\x32\x10' - ), - template_attribute=objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 128 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.AES - ) - ] + # Locate the obj with name 'name0' + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + 'name0', + enums.NameType.UNINTERPRETED_TEXT_STRING + ) ) - ) + ] - response_payload = e._process_derive_key(payload) + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - e._logger.info.assert_any_call("Processing operation: DeriveKey") - e._logger.info.assert_any_call( - "Object 1 will be used as the keying material for the derivation " - "process." - ) - e._logger.info.assert_any_call("Created a SymmetricKey with ID: 2") + e._logger.info.assert_any_call("Processing operation: Locate") - self.assertEqual("2", response_payload.unique_identifier) + self.assertEqual(len(response_payload.unique_identifiers), 2) + self.assertIn(id_a, response_payload.unique_identifiers) + self.assertIn(id_b, response_payload.unique_identifiers) - managed_object = e._data_session.query( - pie_objects.SymmetricKey - ).filter( - pie_objects.SymmetricKey.unique_identifier == 2 - ).one() + # Locate the obj with name 'name1' + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.NAME, + attributes.Name.create( + 'name1', + enums.NameType.UNINTERPRETED_TEXT_STRING + ) + ) + ] - self.assertEqual( - ( - b'\x6B\x77\xB4\xD6\x30\x06\xDE\xE6' - b'\x05\xB1\x56\xE2\x74\x03\x97\x93' - ), - managed_object.value - ) - self.assertEqual( - enums.CryptographicAlgorithm.AES, - managed_object.cryptographic_algorithm - ) - self.assertEqual(128, managed_object.cryptographic_length) - self.assertIsNotNone(managed_object.initial_date) + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - def test_derive_key_invalid_derivation_type(self): + e._logger.info.assert_any_call("Processing operation: Locate") + + self.assertEqual(len(response_payload.unique_identifiers), 1) + self.assertIn(id_c, response_payload.unique_identifiers) + + def test_locate_with_initial_date(self): """ - Test that the right error is thrown when an invalid derivation type - is provided with a DeriveKey request. + Test the Locate operation when 'Initial Date' attributes are given. """ e = engine.KmipEngine() e._data_store = self.engine e._data_store_session_factory = self.session_factory e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - e._cryptography_engine.logger = mock.MagicMock() - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.CERTIFICATE + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - args = (payload, ) - self.assertRaisesRegex( - exceptions.InvalidField, - "Key derivation can only generate a SymmetricKey or SecretData " - "object.", - e._process_derive_key, - *args + obj_a = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name1' + ) + obj_a.initial_date = int(time.time()) + obj_a_time_str = time.strftime( + "%a %b %-2d %H:%M:%S %Y", + time.gmtime(obj_a.initial_date) + ) + + time.sleep(2) + mid_time = int(time.time()) + mid_time_str = time.strftime( + "%a %b %-2d %H:%M:%S %Y", + time.gmtime(mid_time) ) + time.sleep(2) - def test_derive_key_invalid_base_key(self): - """ - Test that the right error is thrown when an object not suitable for - key derivation is provided as the base key with a DeriveKey request. - """ - e = engine.KmipEngine() - e._data_store = self.engine - e._data_store_session_factory = self.session_factory + obj_b = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.DES, + 128, + key, + name='name2' + ) + obj_b.initial_date = int(time.time()) + obj_b_time_str = time.strftime( + "%a %b %-2d %H:%M:%S %Y", + time.gmtime(obj_b.initial_date) + ) + + time.sleep(2) + end_time = int(time.time()) + + e._data_session.add(obj_a) + e._data_session.add(obj_b) + e._data_session.commit() e._data_session = e._data_store_session_factory() - e._is_allowed_by_operation_policy = mock.Mock(return_value=True) - e._logger = mock.MagicMock() - e._cryptography_engine.logger = mock.MagicMock() - invalid_key = pie_objects.OpaqueObject( - b'\x01\x02\x04\x08\x10\x20\x40\x80', - enums.OpaqueDataType.NONE - ) - e._data_session.add(invalid_key) + id_a = str(obj_a.unique_identifier) + id_b = str(obj_b.unique_identifier) + + attribute_factory = factory.AttributeFactory() + + # Locate the object with a specific timestamp + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + obj_a.initial_date + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) e._data_session.commit() e._data_session = e._data_store_session_factory() - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.SECRET_DATA, - unique_identifiers=[str(invalid_key.unique_identifier)] + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: object's initial date ({}) does not match " + "the specified initial date ({}).".format( + obj_b_time_str, + obj_a_time_str + ) ) + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + self.assertEqual(len(response_payload.unique_identifiers), 1) + self.assertIn(id_a, response_payload.unique_identifiers) - args = (payload, ) - self.assertRaisesRegex( - exceptions.InvalidField, - "Object 1 is not a suitable type for key derivation. Please " - "specify a key or secret data.", - e._process_derive_key, - *args + # Locate an object with a timestamp range + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + mid_time + ), + attribute_factory.create_attribute( + enums.AttributeType.INITIAL_DATE, + end_time + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: object's initial date ({}) is less than " + "the starting initial date ({}).".format( + obj_a_time_str, + mid_time_str + ) + ) + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_b) ) + self.assertEqual(len(response_payload.unique_identifiers), 1) + self.assertIn(id_b, response_payload.unique_identifiers) - def test_derive_key_non_derivable_base_key(self): + def test_locate_with_state(self): """ - Test that the right error is thrown when an object suitable for - key derivation but not marked as such is provided as the base key - with a DeriveKey request. + Test the Locate operation when the 'State' attribute is given. """ e = engine.KmipEngine() e._data_store = self.engine @@ -3870,39 +6863,94 @@ e._data_session = e._data_store_session_factory() e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - e._cryptography_engine.logger = mock.MagicMock() - base_key = pie_objects.SymmetricKey( + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + ) + + obj_a = pie_objects.SymmetricKey( enums.CryptographicAlgorithm.AES, 128, - ( - b'\x00\x01\x02\x03\x04\x05\x06\x07' - b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' - ), - [enums.CryptographicUsageMask.ENCRYPT] + key, + name='name1' ) - e._data_session.add(base_key) + obj_a.state = enums.State.PRE_ACTIVE + obj_b = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.DES, + 128, + key, + name='name2' + ) + obj_b.state = enums.State.ACTIVE + + e._data_session.add(obj_a) + e._data_session.add(obj_b) e._data_session.commit() e._data_session = e._data_store_session_factory() - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.SECRET_DATA, - unique_identifiers=[str(base_key.unique_identifier)] + id_a = str(obj_a.unique_identifier) + id_b = str(obj_b.unique_identifier) + + attribute_factory = factory.AttributeFactory() + + # Locate the object with a specific state + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.STATE, + enums.State.PRE_ACTIVE + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified state ({}) does not match " + "the object's state ({}).".format( + enums.State.PRE_ACTIVE.name, + enums.State.ACTIVE.name + ) ) + self.assertEqual(len(response_payload.unique_identifiers), 1) + self.assertIn(id_a, response_payload.unique_identifiers) - args = (payload, ) - self.assertRaisesRegex( - exceptions.InvalidField, - "The DeriveKey bit must be set in the cryptographic usage mask " - "for object 1 for it to be used in key derivation.", - e._process_derive_key, - *args + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.STATE, + enums.State.ACTIVE + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: " + "the specified state ({}) does not match " + "the object's state ({}).".format( + enums.State.ACTIVE.name, + enums.State.PRE_ACTIVE.name + ) ) + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_b) + ) + self.assertEqual(len(response_payload.unique_identifiers), 1) + self.assertIn(id_b, response_payload.unique_identifiers) - def test_derive_key_alternate_derivation_data(self): + def test_locate_with_object_type(self): """ - Test that a DeriveKey request can be processed correctly by - specifying multiple base objects and no derivation data. + Test the Locate operation when the 'Object Type' attribute is given. """ e = engine.KmipEngine() e._data_store = self.engine @@ -3910,120 +6958,200 @@ e._data_session = e._data_store_session_factory() e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - e._cryptography_engine.logger = mock.MagicMock() - base_key = pie_objects.SymmetricKey( - algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, - length=176, - value=( - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b\x0b\x0b' - ), - masks=[enums.CryptographicUsageMask.DERIVE_KEY] + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - e._data_session.add(base_key) - e._data_session.commit() - e._data_session = e._data_store_session_factory() - base_data = pie_objects.SecretData( - value=( - b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7' - b'\xf8\xf9' - ), - data_type=enums.SecretDataType.SEED, - masks=[enums.CryptographicUsageMask.DERIVE_KEY] + obj_a = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name1' ) - e._data_session.add(base_data) + obj_b = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD + ) + + e._data_session.add(obj_a) + e._data_session.add(obj_b) e._data_session.commit() e._data_session = e._data_store_session_factory() + id_a = str(obj_a.unique_identifier) + id_b = str(obj_b.unique_identifier) + attribute_factory = factory.AttributeFactory() - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.SYMMETRIC_KEY, - unique_identifiers=[ - str(base_key.unique_identifier), - str(base_data.unique_identifier) - ], - derivation_method=enums.DerivationMethod.HMAC, - derivation_parameters=attributes.DerivationParameters( - cryptographic_parameters=attributes.CryptographicParameters( - hashing_algorithm=enums.HashingAlgorithm.SHA_256 - ), - salt=( - b'\x00\x01\x02\x03\x04\x05\x06\x07' - b'\x08\x09\x0a\x0b\x0c' - ) - ), - template_attribute=objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 336 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.AES - ) - ] + # Locate the secret data object based on its type. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_TYPE, + enums.ObjectType.SECRET_DATA ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_b) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified object type (SECRET_DATA) does not " + "match the object's object type (SYMMETRIC_KEY)." ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_b, response_payload.unique_identifiers) - response_payload = e._process_derive_key(payload) + # Locate the symmetric key object based on its type. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_TYPE, + enums.ObjectType.SYMMETRIC_KEY + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - e._logger.info.assert_any_call("Processing operation: DeriveKey") - e._logger.info.assert_any_call( - "2 derivation objects specified with the DeriveKey request." - ) - e._logger.info.assert_any_call( - "Object 1 will be used as the keying material for the derivation " - "process." - ) - e._logger.info.assert_any_call( - "Object 2 will be used as the derivation data for the derivation " - "process." + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified object type (SYMMETRIC_KEY) does not " + "match the object's object type (SECRET_DATA)." ) - e._logger.info.assert_any_call("Created a SymmetricKey with ID: 3") + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) - self.assertEqual("3", response_payload.unique_identifier) + # Try to locate a non-existent object by its type. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_TYPE, + enums.ObjectType.PUBLIC_KEY + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - managed_object = e._data_session.query( - pie_objects.SymmetricKey - ).filter( - pie_objects.SymmetricKey.unique_identifier == 3 - ).one() + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: " + "the specified object type (PUBLIC_KEY) does not " + "match the object's object type (SYMMETRIC_KEY)." + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified object type (PUBLIC_KEY) does not " + "match the object's object type (SECRET_DATA)." + ) + self.assertEqual(0, len(response_payload.unique_identifiers)) + + def test_locate_with_cryptographic_algorithm(self): + """ + Test the Locate operation when the 'Cryptographic Algorithm' attribute + is given. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() - self.assertEqual( - ( - b'\x3c\xb2\x5f\x25\xfa\xac\xd5\x7a' - b'\x90\x43\x4f\x64\xd0\x36\x2f\x2a' - b'\x2d\x2d\x0a\x90\xcf\x1a\x5a\x4c' - b'\x5d\xb0\x2d\x56\xec\xc4\xc5\xbf' - b'\x34\x00\x72\x08\xd5\xb8\x87\x18' - b'\x58\x65' - ), - managed_object.value + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - self.assertEqual( + + obj_a = pie_objects.SymmetricKey( enums.CryptographicAlgorithm.AES, - managed_object.cryptographic_algorithm + 128, + key, + name='name1' ) - self.assertEqual( - 336, - managed_object.cryptographic_length + obj_b = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD ) - self.assertIsNotNone(managed_object.initial_date) - def test_derive_key_unspecified_iv(self): - """ - """ - self.skipTest('') + e._data_session.add(obj_a) + e._data_session.add(obj_b) + e._data_session.commit() + e._data_session = e._data_store_session_factory() - def test_derive_key_missing_cryptographic_length(self): + id_a = str(obj_a.unique_identifier) + + attribute_factory = factory.AttributeFactory() + + # Locate the symmetric key object based on its cryptographic algorithm. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.AES + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified attribute (Cryptographic Algorithm) is not " + "applicable for the object's object type (SECRET_DATA)." + ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) + + # Try to locate a non-existent object based on its cryptographic + # algorithm. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, + enums.CryptographicAlgorithm.RSA + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: " + "the specified cryptographic algorithm (RSA) does not match " + "the object's cryptographic algorithm (AES)." + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified attribute (Cryptographic Algorithm) is not " + "applicable for the object's object type (SECRET_DATA)." + ) + self.assertEqual(0, len(response_payload.unique_identifiers)) + + def test_locate_with_cryptographic_length(self): """ - Test that the right error is thrown when the cryptographic length is - missing from a DeriveKey request. + Test the Locate operation when the 'Cryptographic Length' attribute + is given. """ e = engine.KmipEngine() e._data_store = self.engine @@ -4031,57 +7159,87 @@ e._data_session = e._data_store_session_factory() e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - e._cryptography_engine.logger = mock.MagicMock() - base_key = pie_objects.SymmetricKey( - algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, - length=160, - value=( - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b' - ), - masks=[enums.CryptographicUsageMask.DERIVE_KEY] + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - e._data_session.add(base_key) + + obj_a = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name1' + ) + obj_b = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD + ) + + e._data_session.add(obj_a) + e._data_session.add(obj_b) e._data_session.commit() e._data_session = e._data_store_session_factory() + id_a = str(obj_a.unique_identifier) + attribute_factory = factory.AttributeFactory() - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.SYMMETRIC_KEY, - unique_identifiers=[str(base_key.unique_identifier)], - derivation_method=enums.DerivationMethod.HMAC, - derivation_parameters=attributes.DerivationParameters( - cryptographic_parameters=attributes.CryptographicParameters( - hashing_algorithm=enums.HashingAlgorithm.SHA_256 - ), - derivation_data=b'\x48\x69\x20\x54\x68\x65\x72\x65', - ), - template_attribute=objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.AES - ) - ] + # Locate the symmetric key object based on its cryptographic length. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 128 ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified attribute (Cryptographic Length) is not " + "applicable for the object's object type (SECRET_DATA)." ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) - args = (payload, ) - self.assertRaisesRegex( - exceptions.InvalidField, - "The cryptographic length must be provided in the template " - "attribute.", - e._process_derive_key, - *args + # Try to locate a non-existent object based on its cryptographic + # algorithm. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_LENGTH, + 2048 + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: " + "the specified cryptographic length (2048) does not match " + "the object's cryptographic length (128)." + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified attribute (Cryptographic Length) is not " + "applicable for the object's object type (SECRET_DATA)." ) + self.assertEqual(0, len(response_payload.unique_identifiers)) - def test_derive_key_invalid_cryptographic_length(self): + def test_locate_with_cryptographic_usage_masks(self): """ - Test that the right error is thrown when an invalid cryptographic - length is provided with a DeriveKey request. + Test the Locate operation when 'Cryptographic Usage Mask' values are + given. """ e = engine.KmipEngine() e._data_store = self.engine @@ -4089,61 +7247,122 @@ e._data_session = e._data_store_session_factory() e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - e._cryptography_engine.logger = mock.MagicMock() - base_key = pie_objects.SymmetricKey( - algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, - length=160, - value=( - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b' - ), - masks=[enums.CryptographicUsageMask.DERIVE_KEY] + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - e._data_session.add(base_key) + + obj_a = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name1' + ) + obj_a.cryptographic_usage_masks = [ + enums.CryptographicUsageMask.EXPORT, + enums.CryptographicUsageMask.ENCRYPT, + enums.CryptographicUsageMask.DECRYPT + ] + obj_b = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD + ) + obj_b.cryptographic_usage_masks = [ + enums.CryptographicUsageMask.EXPORT + ] + + e._data_session.add(obj_a) + e._data_session.add(obj_b) e._data_session.commit() e._data_session = e._data_store_session_factory() + id_a = str(obj_a.unique_identifier) + id_b = str(obj_b.unique_identifier) + attribute_factory = factory.AttributeFactory() - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.SYMMETRIC_KEY, - unique_identifiers=[str(base_key.unique_identifier)], - derivation_method=enums.DerivationMethod.HMAC, - derivation_parameters=attributes.DerivationParameters( - cryptographic_parameters=attributes.CryptographicParameters( - hashing_algorithm=enums.HashingAlgorithm.SHA_256 - ), - derivation_data=b'\x48\x69\x20\x54\x68\x65\x72\x65', - ), - template_attribute=objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 123 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.AES - ) + # Locate the objects based on their shared cryptographic usage masks. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [enums.CryptographicUsageMask.EXPORT] + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_b) + ) + self.assertEqual(2, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) + self.assertIn(id_b, response_payload.unique_identifiers) + + # Locate the symmetric key based on its unique cryptographic usage + # masks. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.ENCRYPT ] ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: the specified cryptographic usage mask (ENCRYPT) " + "is not set on the object." ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) - args = (payload, ) - self.assertRaisesRegex( - exceptions.InvalidField, - "The cryptographic length must correspond to a valid number of " - "bytes; it must be a multiple of 8.", - e._process_derive_key, - *args + # Try to locate a non-existent object based on its unique cryptographic + # usage masks. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, + [ + enums.CryptographicUsageMask.SIGN + ] + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: the specified cryptographic usage mask (SIGN) " + "is not set on the object." ) + e._logger.debug.assert_any_call( + "Failed match: the specified cryptographic usage mask (SIGN) " + "is not set on the object." + ) + self.assertEqual(0, len(response_payload.unique_identifiers)) - def test_derive_key_missing_cryptographic_algorithm(self): + def test_locate_with_certificate_type(self): """ - Test that the right error is thrown when the cryptographic algorithm - is missing from a DeriveKey request when deriving a symmetric key. + Test the Locate operation when the 'Certificate Type' attribute is + given. """ e = engine.KmipEngine() e._data_store = self.engine @@ -4151,57 +7370,86 @@ e._data_session = e._data_store_session_factory() e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - e._cryptography_engine.logger = mock.MagicMock() - base_key = pie_objects.SymmetricKey( - algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, - length=160, - value=( - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b' - ), - masks=[enums.CryptographicUsageMask.DERIVE_KEY] + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - e._data_session.add(base_key) + + obj_a = pie_objects.Certificate( + enums.CertificateType.X_509, + b'', + name='certificate1' + ) + obj_b = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD + ) + + e._data_session.add(obj_a) + e._data_session.add(obj_b) e._data_session.commit() e._data_session = e._data_store_session_factory() + id_a = str(obj_a.unique_identifier) + attribute_factory = factory.AttributeFactory() - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.SYMMETRIC_KEY, - unique_identifiers=[str(base_key.unique_identifier)], - derivation_method=enums.DerivationMethod.HMAC, - derivation_parameters=attributes.DerivationParameters( - cryptographic_parameters=attributes.CryptographicParameters( - hashing_algorithm=enums.HashingAlgorithm.SHA_256 - ), - derivation_data=b'\x48\x69\x20\x54\x68\x65\x72\x65', - ), - template_attribute=objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 256 - ) - ] + # Locate the certificate object based on its certificate type. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.CERTIFICATE_TYPE, + enums.CertificateType.X_509 ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified attribute (Certificate Type) is not " + "applicable for the object's object type (SECRET_DATA)." ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) - args = (payload, ) - self.assertRaisesRegex( - exceptions.InvalidField, - "The cryptographic algorithm must be provided in the template " - "attribute when deriving a symmetric key.", - e._process_derive_key, - *args + # Try to locate a non-existent object based on its certificate + # type. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.CERTIFICATE_TYPE, + enums.CertificateType.PGP + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: " + "the specified certificate type (PGP) does not match " + "the object's certificate type (X_509)." + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified attribute (Certificate Type) is not " + "applicable for the object's object type (SECRET_DATA)." ) + self.assertEqual(0, len(response_payload.unique_identifiers)) - def test_derive_key_oversized_cryptographic_length(self): + def test_locate_with_unique_identifier(self): """ - Test that the right error is thrown when an invalid cryptographic - length is provided with a DeriveKey request. + Test the Locate operation when the 'Unique Identifier' attribute + is given. """ e = engine.KmipEngine() e._data_store = self.engine @@ -4209,66 +7457,113 @@ e._data_session = e._data_store_session_factory() e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - e._cryptography_engine.logger = mock.MagicMock() - base_key = pie_objects.SymmetricKey( - algorithm=enums.CryptographicAlgorithm.HMAC_SHA256, - length=160, - value=( - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' - b'\x0b\x0b\x0b\x0b' - ), - masks=[enums.CryptographicUsageMask.DERIVE_KEY] + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - e._data_session.add(base_key) + + obj_a = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name1' + ) + obj_b = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD + ) + + e._data_session.add(obj_a) + e._data_session.add(obj_b) e._data_session.commit() e._data_session = e._data_store_session_factory() - e._cryptography_engine = mock.MagicMock() - e._cryptography_engine.derive_key.return_value = b'' + id_a = str(obj_a.unique_identifier) + id_b = str(obj_b.unique_identifier) attribute_factory = factory.AttributeFactory() - payload = payloads.DeriveKeyRequestPayload( - object_type=enums.ObjectType.SYMMETRIC_KEY, - unique_identifiers=[str(base_key.unique_identifier)], - derivation_method=enums.DerivationMethod.HMAC, - derivation_parameters=attributes.DerivationParameters( - cryptographic_parameters=attributes.CryptographicParameters( - hashing_algorithm=enums.HashingAlgorithm.SHA_256 - ), - derivation_data=b'\x48\x69\x20\x54\x68\x65\x72\x65', - ), - template_attribute=objects.TemplateAttribute( - attributes=[ - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_LENGTH, - 256 - ), - attribute_factory.create_attribute( - enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, - enums.CryptographicAlgorithm.AES - ) - ] + # Locate the symmetric key object based on its unique identifier. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + id_a ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified unique identifier ({}) does not match " + "the object's unique identifier ({}).".format(id_a, id_b) ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) - args = (payload, ) - self.assertRaisesRegex( - exceptions.CryptographicFailure, - "The specified length exceeds the output of the derivation " - "method.", - e._process_derive_key, - *args + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + id_b + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: " + "the specified unique identifier ({}) does not match " + "the object's unique identifier ({}).".format(id_b, id_a) ) + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_b) + ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_b, response_payload.unique_identifiers) - def test_locate(self): + # Try to locate a non-existent object based on its cryptographic + # algorithm. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.UNIQUE_IDENTIFIER, + "unknown" + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: " + "the specified unique identifier ({}) does not match " + "the object's unique identifier ({}).".format("unknown", id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified unique identifier ({}) does not match " + "the object's unique identifier ({}).".format("unknown", id_b) + ) + self.assertEqual(0, len(response_payload.unique_identifiers)) + + def test_locate_with_operation_policy_name(self): """ - Test that a Locate request can be processed correctly. + Test the Locate operation when the 'Operation Policy Name' attribute + is given. """ - # TODO Need add more extensive tests after locate operaton is - # fully supported e = engine.KmipEngine() e._data_store = self.engine e._data_store_session_factory = self.session_factory @@ -4276,82 +7571,111 @@ e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - obj_a = pie_objects.OpaqueObject(b'', enums.OpaqueDataType.NONE) - obj_b = pie_objects.OpaqueObject(b'', enums.OpaqueDataType.NONE) - - # locate should return nothing at beginning - payload = payloads.LocateRequestPayload() - response_payload = e._process_locate(payload) - e._data_session.commit() - e._data_session = e._data_store_session_factory() + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + ) - e._logger.info.assert_any_call( - "Processing operation: Locate" + obj_a = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name1' ) - self.assertEqual( - len(response_payload.unique_identifiers), - 0 + obj_a.operation_policy_name = "default" + obj_b = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD ) + obj_b.operation_policy_name = "custom" - # Add the first obj and test the locate e._data_session.add(obj_a) + e._data_session.add(obj_b) e._data_session.commit() e._data_session = e._data_store_session_factory() id_a = str(obj_a.unique_identifier) + id_b = str(obj_b.unique_identifier) - payload = payloads.LocateRequestPayload() + attribute_factory = factory.AttributeFactory() + + # Locate the symmetric key object based on its unique identifier. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + "default" + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) e._logger.reset_mock() response_payload = e._process_locate(payload) e._data_session.commit() e._data_session = e._data_store_session_factory() - e._logger.info.assert_any_call( - "Processing operation: Locate" - ) - - self.assertEqual( - len(response_payload.unique_identifiers), - 1 - ) - self.assertEqual( - id_a, - response_payload.unique_identifiers[0] + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified operation policy name (default) does not match " + "the object's operation policy name (custom)." ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) - # Add the second obj and test the locate - e._data_session.add(obj_b) + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + "custom" + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) e._data_session.commit() e._data_session = e._data_store_session_factory() - id_b = str(obj_b.unique_identifier) + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_b) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified operation policy name (custom) does not match " + "the object's operation policy name (default)." + ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_b, response_payload.unique_identifiers) - payload = payloads.LocateRequestPayload() + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.OPERATION_POLICY_NAME, + "unknown" + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) e._logger.reset_mock() response_payload = e._process_locate(payload) e._data_session.commit() e._data_session = e._data_store_session_factory() - e._logger.info.assert_any_call( - "Processing operation: Locate" - ) - - self.assertEqual( - len(response_payload.unique_identifiers), - 2 - ) - self.assertIn( - id_a, - response_payload.unique_identifiers - ) - self.assertIn( - id_b, - response_payload.unique_identifiers + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Failed match: " + "the specified operation policy name (unknown) does not match " + "the object's operation policy name (default)." + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified operation policy name (unknown) does not match " + "the object's operation policy name (custom)." ) + self.assertEqual(0, len(response_payload.unique_identifiers)) - def test_locate_with_name(self): + def test_locate_with_application_specific_information(self): """ - Test locate operation when 'Name' attribute is given. + Test the Locate operation when the 'Application Specific Information' + attribute is given. """ e = engine.KmipEngine() e._data_store = self.engine @@ -4360,14 +7684,36 @@ e._is_allowed_by_operation_policy = mock.Mock(return_value=True) e._logger = mock.MagicMock() - key = (b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00') + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + ) + + app_specific_info_a = pie_objects.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.example.com" + ) + app_specific_info_b = pie_objects.ApplicationSpecificInformation( + application_namespace="ssl", + application_data="www.test.com" + ) + obj_a = pie_objects.SymmetricKey( - enums.CryptographicAlgorithm.AES, 128, key, name='name0') - obj_b = pie_objects.SymmetricKey( - enums.CryptographicAlgorithm.DES, 128, key, name='name0') - obj_c = pie_objects.SymmetricKey( - enums.CryptographicAlgorithm.AES, 128, key, name='name1') + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name1' + ) + obj_a.app_specific_info.append(app_specific_info_a) + obj_a.app_specific_info.append(app_specific_info_b) + obj_b = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD + ) + obj_b.app_specific_info.append(app_specific_info_a) + obj_c = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD + ) e._data_session.add(obj_a) e._data_session.add(obj_b) @@ -4377,73 +7723,178 @@ id_a = str(obj_a.unique_identifier) id_b = str(obj_b.unique_identifier) - id_c = str(obj_c.unique_identifier) attribute_factory = factory.AttributeFactory() - # Locate the obj with name 'name0' + # Locate the symmetric key objects based on their shared application + # specific information attribute. attrs = [ - attribute_factory.create_attribute( - enums.AttributeType.NAME, - attributes.Name.create( - 'name0', - enums.NameType.UNINTERPRETED_TEXT_STRING - ) - ), + attribute_factory.create_attribute( + enums.AttributeType.APPLICATION_SPECIFIC_INFORMATION, + { + "application_namespace": "ssl", + "application_data": "www.example.com" + } + ) ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_b) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified application specific " + "information ('ssl', 'www.example.com') does not match any " + "of the object's associated application " + "specific information attributes." + ) + self.assertEqual(2, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) + self.assertIn(id_b, response_payload.unique_identifiers) + # Locate a single symmetric key object based on its unique application + # specific information attribute. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.APPLICATION_SPECIFIC_INFORMATION, + { + "application_namespace": "ssl", + "application_data": "www.test.com" + } + ) + ] payload = payloads.LocateRequestPayload(attributes=attrs) e._logger.reset_mock() response_payload = e._process_locate(payload) e._data_session.commit() e._data_session = e._data_store_session_factory() - e._logger.info.assert_any_call( - "Processing operation: Locate" + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified application specific " + "information ('ssl', 'www.test.com') does not match any " + "of the object's associated application " + "specific information attributes." ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) - self.assertEqual( - len(response_payload.unique_identifiers), - 2 + def test_locate_with_object_group(self): + """ + Test the Locate operation when the 'Object Group' + attribute is given. + """ + e = engine.KmipEngine() + e._data_store = self.engine + e._data_store_session_factory = self.session_factory + e._data_session = e._data_store_session_factory() + e._is_allowed_by_operation_policy = mock.Mock(return_value=True) + e._logger = mock.MagicMock() + + key = ( + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) - self.assertIn( - id_a, - response_payload.unique_identifiers + + object_group_a = pie_objects.ObjectGroup(object_group="Group1") + object_group_b = pie_objects.ObjectGroup(object_group="Group2") + + obj_a = pie_objects.SymmetricKey( + enums.CryptographicAlgorithm.AES, + 128, + key, + name='name1' ) - self.assertIn( - id_b, - response_payload.unique_identifiers + obj_a.object_groups.append(object_group_a) + obj_b = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD + ) + obj_b.object_groups.append(object_group_a) + obj_b.object_groups.append(object_group_b) + obj_c = pie_objects.SecretData( + key, + enums.SecretDataType.PASSWORD ) - # Locate the obj with name 'name1' + e._data_session.add(obj_a) + e._data_session.add(obj_b) + e._data_session.add(obj_c) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + id_a = str(obj_a.unique_identifier) + id_b = str(obj_b.unique_identifier) + + attribute_factory = factory.AttributeFactory() + + # Locate the symmetric key objects based on their shared object group + # attribute. attrs = [ - attribute_factory.create_attribute( - enums.AttributeType.NAME, - attributes.Name.create( - 'name1', - enums.NameType.UNINTERPRETED_TEXT_STRING - ) - ), + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Group1" + ) ] - payload = payloads.LocateRequestPayload(attributes=attrs) e._logger.reset_mock() response_payload = e._process_locate(payload) e._data_session.commit() e._data_session = e._data_store_session_factory() - e._logger.info.assert_any_call( - "Processing operation: Locate" - ) + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_a) + ) + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_b) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified object group ('Group1') does not match any " + "of the object's associated object group attributes." + ) + self.assertEqual(2, len(response_payload.unique_identifiers)) + self.assertIn(id_a, response_payload.unique_identifiers) + self.assertIn(id_b, response_payload.unique_identifiers) - self.assertEqual( - len(response_payload.unique_identifiers), - 1 - ) - self.assertIn( - id_c, - response_payload.unique_identifiers + # Locate a single symmetric key object based on its unique object group + # attribute. + attrs = [ + attribute_factory.create_attribute( + enums.AttributeType.OBJECT_GROUP, + "Group2" + ) + ] + payload = payloads.LocateRequestPayload(attributes=attrs) + e._logger.reset_mock() + response_payload = e._process_locate(payload) + e._data_session.commit() + e._data_session = e._data_store_session_factory() + + e._logger.info.assert_any_call("Processing operation: Locate") + e._logger.debug.assert_any_call( + "Locate filter matched object: {}".format(id_b) + ) + e._logger.debug.assert_any_call( + "Failed match: " + "the specified object group ('Group2') does not match any " + "of the object's associated object group attributes." ) + self.assertEqual(1, len(response_payload.unique_identifiers)) + self.assertIn(id_b, response_payload.unique_identifiers) def test_get(self): """ diff -Nru python-pykmip-0.9.1/kmip/tests/unit/services/server/test_policy.py python-pykmip-0.10.0/kmip/tests/unit/services/server/test_policy.py --- python-pykmip-0.9.1/kmip/tests/unit/services/server/test_policy.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/services/server/test_policy.py 2020-02-25 16:05:27.000000000 +0000 @@ -76,6 +76,34 @@ result = rules.is_attribute_deprecated(attribute_b) self.assertTrue(result) + def test_is_attribute_deletable_by_client(self): + """ + Test that is_attribute_deletable_by_client returns the expected + results in all cases. + """ + rules = policy.AttributePolicy(contents.ProtocolVersion(1, 0)) + + self.assertFalse( + rules.is_attribute_deletable_by_client("Cryptographic Algorithm") + ) + self.assertTrue( + rules.is_attribute_deletable_by_client("Contact Information") + ) + + def test_is_attribute_modifiable_by_client(self): + """ + Test that is_attribute_modifiable_by_client returns the expected + results in all cases. + """ + rules = policy.AttributePolicy(contents.ProtocolVersion(1, 0)) + + self.assertFalse( + rules.is_attribute_modifiable_by_client("Unique Identifier") + ) + self.assertTrue( + rules.is_attribute_modifiable_by_client("Name") + ) + def test_is_attribute_applicable_to_object_type(self): """ Test that is_attribute_applicable_to_object_type returns the @@ -158,7 +186,8 @@ 'Application Specific Information', 'Contact Information', 'Last Change Date', - 'Custom Attribute' + 'Custom Attribute', + "Sensitive" ] result = rules.get_all_attribute_names() diff -Nru python-pykmip-0.9.1/kmip/tests/unit/services/test_kmip_client.py python-pykmip-0.10.0/kmip/tests/unit/services/test_kmip_client.py --- python-pykmip-0.9.1/kmip/tests/unit/services/test_kmip_client.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/tests/unit/services/test_kmip_client.py 2020-02-25 16:05:27.000000000 +0000 @@ -30,6 +30,8 @@ from kmip.core.enums import CryptographicAlgorithm as \ CryptographicAlgorithmEnum +from kmip.core import exceptions + from kmip.core.factories.attributes import AttributeFactory from kmip.core.factories.credentials import CredentialFactory from kmip.core.factories.secrets import SecretFactory @@ -773,6 +775,315 @@ self.assertEqual(ssl.SSLSocket, type(self.client.socket)) @mock.patch( + "kmip.services.kmip_client.KMIPProxy._build_request_message" + ) + @mock.patch( + "kmip.services.kmip_client.KMIPProxy._send_and_receive_message" + ) + def test_send_request_payload(self, send_mock, build_mock): + """ + Test that the client can send a request payload and correctly handle + the resulting response messsage. + """ + request_payload = payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_name="Object Group", + attribute_index=2 + ) + response_payload = payloads.DeleteAttributeResponsePayload( + unique_identifier="1", + attribute=None + ) + + batch_item = ResponseBatchItem( + operation=Operation(OperationEnum.DELETE_ATTRIBUTE), + result_status=ResultStatus(ResultStatusEnum.SUCCESS), + response_payload=response_payload + ) + response_message = ResponseMessage(batch_items=[batch_item]) + + build_mock.return_value = None + send_mock.return_value = response_message + + result = self.client.send_request_payload( + OperationEnum.DELETE_ATTRIBUTE, + request_payload + ) + + self.assertIsInstance(result, payloads.DeleteAttributeResponsePayload) + self.assertEqual(result, response_payload) + + def test_send_request_payload_invalid_payload(self): + """ + Test that a TypeError is raised when an invalid payload is used to + send a request. + """ + args = (OperationEnum.DELETE_ATTRIBUTE, "invalid") + self.assertRaisesRegex( + TypeError, + "The request payload must be a RequestPayload object.", + self.client.send_request_payload, + *args + ) + + def test_send_request_payload_mismatch_operation_payload(self): + """ + Test that a TypeError is raised when the operation and request payload + do not match up when used to send a request. + """ + args = ( + OperationEnum.DELETE_ATTRIBUTE, + payloads.CreateRequestPayload() + ) + self.assertRaisesRegex( + TypeError, + "The request payload for the DeleteAttribute operation must be a " + "DeleteAttributeRequestPayload object.", + self.client.send_request_payload, + *args + ) + + args = ( + OperationEnum.SET_ATTRIBUTE, + payloads.CreateRequestPayload() + ) + self.assertRaisesRegex( + TypeError, + "The request payload for the SetAttribute operation must be a " + "SetAttributeRequestPayload object.", + self.client.send_request_payload, + *args + ) + + args = ( + OperationEnum.MODIFY_ATTRIBUTE, + payloads.CreateRequestPayload() + ) + self.assertRaisesRegex( + TypeError, + "The request payload for the ModifyAttribute operation must be a " + "ModifyAttributeRequestPayload object.", + self.client.send_request_payload, + *args + ) + + @mock.patch( + "kmip.services.kmip_client.KMIPProxy._build_request_message" + ) + @mock.patch( + "kmip.services.kmip_client.KMIPProxy._send_and_receive_message" + ) + def test_send_request_payload_incorrect_number_of_batch_items( + self, + send_mock, + build_mock + ): + """ + Test that an InvalidMessage error is raised when the wrong number of + response payloads are returned from the server. + """ + build_mock.return_value = None + send_mock.return_value = ResponseMessage(batch_items=[]) + + args = ( + OperationEnum.DELETE_ATTRIBUTE, + payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_name="Object Group", + attribute_index=2 + ) + ) + + self.assertRaisesRegex( + exceptions.InvalidMessage, + "The response message does not have the right number of requested " + "operation results.", + self.client.send_request_payload, + *args + ) + + @mock.patch( + "kmip.services.kmip_client.KMIPProxy._build_request_message" + ) + @mock.patch( + "kmip.services.kmip_client.KMIPProxy._send_and_receive_message" + ) + def test_send_request_payload_mismatch_response_operation( + self, + send_mock, + build_mock + ): + """ + Test that an InvalidMessage error is raised when the wrong operation + is returned from the server. + """ + response_payload = payloads.DeleteAttributeResponsePayload( + unique_identifier="1", + attribute=None + ) + + batch_item = ResponseBatchItem( + operation=Operation(OperationEnum.CREATE), + result_status=ResultStatus(ResultStatusEnum.SUCCESS), + response_payload=response_payload + ) + build_mock.return_value = None + send_mock.return_value = ResponseMessage(batch_items=[batch_item]) + + args = ( + OperationEnum.DELETE_ATTRIBUTE, + payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_name="Object Group", + attribute_index=2 + ) + ) + + self.assertRaisesRegex( + exceptions.InvalidMessage, + "The response message does not match the request operation.", + self.client.send_request_payload, + *args + ) + + @mock.patch( + "kmip.services.kmip_client.KMIPProxy._build_request_message" + ) + @mock.patch( + "kmip.services.kmip_client.KMIPProxy._send_and_receive_message" + ) + def test_send_request_payload_mismatch_response_payload( + self, + send_mock, + build_mock + ): + """ + Test that an InvalidMessage error is raised when the wrong payload + is returned from the server. + """ + response_payload = payloads.DestroyResponsePayload( + unique_identifier="1" + ) + + batch_item = ResponseBatchItem( + operation=Operation(OperationEnum.DELETE_ATTRIBUTE), + result_status=ResultStatus(ResultStatusEnum.SUCCESS), + response_payload=response_payload + ) + build_mock.return_value = None + send_mock.return_value = ResponseMessage(batch_items=[batch_item]) + + args = ( + OperationEnum.DELETE_ATTRIBUTE, + payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_name="Object Group", + attribute_index=2 + ) + ) + self.assertRaisesRegex( + exceptions.InvalidMessage, + "Invalid response payload received for the DeleteAttribute " + "operation.", + self.client.send_request_payload, + *args + ) + + # Test SetAttribute + batch_item = ResponseBatchItem( + operation=Operation(OperationEnum.SET_ATTRIBUTE), + result_status=ResultStatus(ResultStatusEnum.SUCCESS), + response_payload=response_payload + ) + send_mock.return_value = ResponseMessage(batch_items=[batch_item]) + args = ( + OperationEnum.SET_ATTRIBUTE, + payloads.SetAttributeRequestPayload( + unique_identifier="1", + new_attribute=objects.NewAttribute( + attribute=primitives.Boolean( + value=True, + tag=enums.Tags.SENSITIVE + ) + ) + ) + ) + self.assertRaisesRegex( + exceptions.InvalidMessage, + "Invalid response payload received for the SetAttribute " + "operation.", + self.client.send_request_payload, + *args + ) + + # Test ModifyAttribute + batch_item = ResponseBatchItem( + operation=Operation(OperationEnum.MODIFY_ATTRIBUTE), + result_status=ResultStatus(ResultStatusEnum.SUCCESS), + response_payload=response_payload + ) + send_mock.return_value = ResponseMessage(batch_items=[batch_item]) + args = ( + OperationEnum.MODIFY_ATTRIBUTE, + payloads.ModifyAttributeRequestPayload( + unique_identifier="1", + new_attribute=objects.NewAttribute( + attribute=primitives.Boolean( + value=True, + tag=enums.Tags.SENSITIVE + ) + ) + ) + ) + self.assertRaisesRegex( + exceptions.InvalidMessage, + "Invalid response payload received for the ModifyAttribute " + "operation.", + self.client.send_request_payload, + *args + ) + + @mock.patch( + "kmip.services.kmip_client.KMIPProxy._build_request_message" + ) + @mock.patch( + "kmip.services.kmip_client.KMIPProxy._send_and_receive_message" + ) + def test_send_request_payload_operation_failure( + self, + send_mock, + build_mock + ): + """ + Test that a KmipOperationFailure error is raised when a payload + with a failure status is returned. + """ + batch_item = ResponseBatchItem( + operation=Operation(OperationEnum.DELETE_ATTRIBUTE), + result_status=ResultStatus(ResultStatusEnum.OPERATION_FAILED), + result_reason=ResultReason(ResultReasonEnum.GENERAL_FAILURE), + result_message=ResultMessage("Test failed!") + ) + build_mock.return_value = None + send_mock.return_value = ResponseMessage(batch_items=[batch_item]) + + args = ( + OperationEnum.DELETE_ATTRIBUTE, + payloads.DeleteAttributeRequestPayload( + unique_identifier="1", + attribute_name="Object Group", + attribute_index=2 + ) + ) + + self.assertRaisesRegex( + exceptions.OperationFailure, + "Test failed!", + self.client.send_request_payload, + *args + ) + + @mock.patch( 'kmip.services.kmip_client.KMIPProxy._build_request_message' ) @mock.patch( diff -Nru python-pykmip-0.9.1/kmip/version.py python-pykmip-0.10.0/kmip/version.py --- python-pykmip-0.9.1/kmip/version.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/kmip/version.py 2020-02-25 16:05:27.000000000 +0000 @@ -13,4 +13,4 @@ # License for the specific language governing permissions and limitations # under the License. -__version__ = "0.9.1" +__version__ = "0.10.0" diff -Nru python-pykmip-0.9.1/requirements.txt python-pykmip-0.10.0/requirements.txt --- python-pykmip-0.9.1/requirements.txt 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/requirements.txt 2020-02-25 16:05:27.000000000 +0000 @@ -1,5 +1,5 @@ cryptography>=1.4 -enum34 +enum-compat requests six>=1.11.0 sqlalchemy>=1.0 diff -Nru python-pykmip-0.9.1/setup.py python-pykmip-0.10.0/setup.py --- python-pykmip-0.9.1/setup.py 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/setup.py 2020-02-25 16:05:27.000000000 +0000 @@ -50,7 +50,7 @@ }, install_requires=[ "cryptography", - "enum34", + "enum-compat", "requests", "six", "sqlalchemy" diff -Nru python-pykmip-0.9.1/test-requirements.txt python-pykmip-0.10.0/test-requirements.txt --- python-pykmip-0.9.1/test-requirements.txt 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/test-requirements.txt 2020-02-25 16:05:27.000000000 +0000 @@ -6,4 +6,5 @@ mock slugs sphinx +PyYAML<=5.2 bandit==1.5.1 diff -Nru python-pykmip-0.9.1/tox.ini python-pykmip-0.10.0/tox.ini --- python-pykmip-0.9.1/tox.ini 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/tox.ini 2020-02-25 16:05:27.000000000 +0000 @@ -18,14 +18,12 @@ [testenv:integration] # Note: This requires local or remote access to a KMIP appliance or service deps = {[testenv]deps} -basepython=python2.7 commands = py.test --strict kmip/tests/integration -m "not ignore" {posargs} [testenv:functional] # Note: This requires local access to instances of the PyKMIP server and SLUGS. deps = {[testenv]deps} -basepython=python2.7 commands = py.test --strict kmip/tests/functional -m "not ignore" {posargs} @@ -38,7 +36,6 @@ deps = sphinx sphinx_rtd_theme -basepython = python2.7 commands = sphinx-build -j4 -T -W -b html -d {envtmpdir}/doctrees source {envtmpdir}/html @@ -46,7 +43,6 @@ changedir = docs extras = deps = {[testenv:docs]deps} -basepython = python2.7 commands = sphinx-build -W -b linkcheck source {envtmpdir}/html diff -Nru python-pykmip-0.9.1/.travis.yml python-pykmip-0.10.0/.travis.yml --- python-pykmip-0.9.1/.travis.yml 2019-06-21 20:16:11.000000000 +0000 +++ python-pykmip-0.10.0/.travis.yml 2020-02-25 16:05:27.000000000 +0000 @@ -223,7 +223,19 @@ # For more info, see: https://github.com/OpenKMIP/PyKMIP/issues/435 - pip uninstall -y six - pip install six>=1.11.0 - - pip install tox + # Pin more_itertools to <= 7.2.0. Version 8.0.0+ drops support for + # Python 3.4. Once PyKMIP drops support for Python 3.4, remove this. + - pip install "more_itertools<=7.2.0" + # Pin tox to <= 3.14.1. Version 3.14.2+ moves to newer versions of + # the importlib_metadata package which breaks on Python 3.4. Future + # versions of tox will also drop support for Python 2.7 and 3.4. + # Once PyKMIP also drops support for Python 2.7 and 3.4, remove this. + - pip install "tox<=3.14.1" + # Pin PyYAML to <= 5.2. Version 5.3+ removes support for Python 3.4. + # PyYAML is installed as a bandit dependency, so while it doesn't + # break PyKMIP proper, it does break any of our test runs using + # Python 3.4. Once PyKMIP drops support for Python 3.4, remove this. + - pip install "PyYAML<=5.2" - pip install bandit - pip install codecov - pip install slugs