diff --git a/kmip/pie/objects.py b/kmip/pie/objects.py index 077fdd9..39a29fd 100644 --- a/kmip/pie/objects.py +++ b/kmip/pie/objects.py @@ -16,10 +16,14 @@ from abc import ABCMeta from abc import abstractmethod -from six import add_metaclass +import six + +from kmip.core.enums import CryptographicAlgorithm +from kmip.core.enums import CryptographicUsageMask +from kmip.core.enums import ObjectType -@add_metaclass(ABCMeta) +@six.add_metaclass(ABCMeta) class ManagedObject: """ The abstract base class of the simplified KMIP object hierarchy. @@ -83,6 +87,13 @@ class ManagedObject: """ raise AttributeError("object type cannot be set") + @abstractmethod + def validate(self): + """ + Verify that the contents of the ManagedObject are valid. + """ + pass + @abstractmethod def __repr__(self): pass @@ -123,7 +134,7 @@ class CryptographicObject(ManagedObject): super(CryptographicObject, self).__init__() - self.crpytographic_usage_masks = list() + self.cryptographic_usage_masks = list() # All remaining attributes are not considered part of the public API # and are subject to change. @@ -176,3 +187,129 @@ class Key(CryptographicObject): # The following attributes are placeholders for attributes that are # unsupported by kmip.core self._usage_limits = None + + +class SymmetricKey(Key): + """ + The SymmetricKey class of the simplified KMIP object hierarchy. + + A SymmetricKey is a core KMIP object that is the subject of key + management operations. For more information, see Section 2.2 of the KMIP + 1.1 specification. + + Attributes: + cryptographic_algorithm: The type of algorithm for the SymmetricKey. + cryptographic_length: The length in bits of the SymmetricKey value. + value: The bytes of the SymmetricKey. + cryptographic_usage_masks: The list of usage mask flags for + SymmetricKey application. + names: The string names of the SymmetricKey. + """ + + def __init__(self, algorithm, length, value, masks=None, + name='Symmetric Key'): + """ + Create a SymmetricKey. + + Args: + algorithm(CryptographicAlgorithm): An enumeration identifying the + type of algorithm for the key. + length(int): The length in bits of the key. + value(bytes): The bytes representing the key. + masks(list): A list of CryptographicUsageMask enumerations defining + how the key will be used. + name(string): The string name of the key. + """ + super(SymmetricKey, self).__init__() + + self._object_type = ObjectType.SYMMETRIC_KEY + + self.value = value + self.cryptographic_algorithm = algorithm + self.cryptographic_length = length + self.names = [name] + + if masks: + self.cryptographic_usage_masks = masks + else: + self.cryptographic_usage_masks = list() + + # All remaining attributes are not considered part of the public API + # and are subject to change. + + # The following attributes are placeholders for attributes that are + # unsupported by kmip.core + self._process_start_date = None + self._protect_stop_date = None + + self.validate() + + def validate(self): + """ + Verify that the contents of the SymmetricKey object are valid. + + Raises: + TypeError: if the types of any SymmetricKey attributes are invalid + ValueError: if the key length and key value length do not match + """ + if not isinstance(self.value, bytes): + raise TypeError("key value must be bytes") + elif not isinstance(self.cryptographic_algorithm, + CryptographicAlgorithm): + raise TypeError("key algorithm must be a CryptographicAlgorithm " + "enumeration") + elif not isinstance(self.cryptographic_length, six.integer_types): + raise TypeError("key length must be an integer") + elif not isinstance(self.cryptographic_usage_masks, list): + raise TypeError("key usage masks must be a list") + + mask_count = len(self.cryptographic_usage_masks) + for i in range(mask_count): + mask = self.cryptographic_usage_masks[i] + if not isinstance(mask, CryptographicUsageMask): + position = "({0} in list)".format(i) + raise TypeError( + "key mask {0} must be a CryptographicUsageMask " + "enumeration".format(position)) + + name_count = len(self.names) + for i in range(name_count): + name = self.names[i] + if not isinstance(name, six.string_types): + position = "({0} in list)".format(i) + raise TypeError("key name {0} must be a string".format( + position)) + + if (len(self.value) * 8) != self.cryptographic_length: + msg = "key length ({0}) not equal to key value length ({1})" + msg = msg.format(self.cryptographic_length, len(self.value) * 8) + raise ValueError(msg) + + def __repr__(self): + algorithm = "algorithm={0}".format(self.cryptographic_algorithm) + length = "length={0}".format(self.cryptographic_length) + value = "value={0}".format(self.value) + + return "SymmetricKey({0}, {1}, {2})".format(algorithm, length, value) + + def __str__(self): + return str(self.value) + + def __eq__(self, other): + if isinstance(other, SymmetricKey): + if self.value != other.value: + return False + elif self.cryptographic_algorithm != other.cryptographic_algorithm: + return False + elif self.cryptographic_length != other.cryptographic_length: + return False + else: + return True + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, SymmetricKey): + return not (self == other) + else: + return NotImplemented diff --git a/kmip/tests/unit/pie/objects/test_cryptographic_object.py b/kmip/tests/unit/pie/objects/test_cryptographic_object.py index 12d2a3a..c1f0813 100644 --- a/kmip/tests/unit/pie/objects/test_cryptographic_object.py +++ b/kmip/tests/unit/pie/objects/test_cryptographic_object.py @@ -30,6 +30,9 @@ class DummyCryptographicObject(CryptographicObject): """ super(DummyCryptographicObject, self).__init__() + def validate(self): + return + def __repr__(self): return '' diff --git a/kmip/tests/unit/pie/objects/test_key.py b/kmip/tests/unit/pie/objects/test_key.py index 0ad1330..d315940 100644 --- a/kmip/tests/unit/pie/objects/test_key.py +++ b/kmip/tests/unit/pie/objects/test_key.py @@ -30,6 +30,9 @@ class DummyKey(Key): """ super(DummyKey, self).__init__() + def validate(self): + return + def __repr__(self): return '' diff --git a/kmip/tests/unit/pie/objects/test_managed_object.py b/kmip/tests/unit/pie/objects/test_managed_object.py index 790f04e..3f860fe 100644 --- a/kmip/tests/unit/pie/objects/test_managed_object.py +++ b/kmip/tests/unit/pie/objects/test_managed_object.py @@ -35,6 +35,10 @@ class DummyManagedObject(ManagedObject): self._object_type = object_type + def validate(self): + super(DummyManagedObject, self).validate() + return + def __repr__(self): super(DummyManagedObject, self).__repr__() return '' @@ -94,6 +98,13 @@ class TestManagedObject(TestCase): self.assertRaises(AttributeError, set_object_type) + def test_validate(self): + """ + Test that validate can be called on a ManagedObject. + """ + dummy = DummyManagedObject() + dummy.validate() + def test_repr(self): """ Test that repr can be applied to a ManagedObject. diff --git a/kmip/tests/unit/pie/objects/test_symmetric_key.py b/kmip/tests/unit/pie/objects/test_symmetric_key.py new file mode 100644 index 0000000..95c1807 --- /dev/null +++ b/kmip/tests/unit/pie/objects/test_symmetric_key.py @@ -0,0 +1,304 @@ +# Copyright (c) 2015 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 testtools import TestCase + +from kmip.core.enums import CryptographicAlgorithm +from kmip.core.enums import CryptographicUsageMask +from kmip.core.enums import ObjectType + +from kmip.pie.objects import SymmetricKey + + +class TestSymmetricKey(TestCase): + """ + Test suite for SymmetricKey. + """ + + def setUp(self): + super(TestSymmetricKey, self).setUp() + + # Key values taken from Sections 14.2, 15.2, and 18.1 of the KMIP 1.1 + # testing documentation. + self.bytes_128a = ( + b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E' + b'\x0F') + self.bytes_128b = ( + b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE' + b'\xFF') + self.bytes_256a = ( + b'\x00\x00\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77' + b'\x88\x88\x99\x99\xAA\xAA\xBB\xBB\xCC\xCC\xDD\xDD\xEE\xEE\xFF' + b'\xFF') + self.bytes_256b = ( + b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + b'\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E' + b'\x1F') + + def tearDown(self): + super(TestSymmetricKey, self).tearDown() + + def test_init(self): + """ + Test that a SymmetricKey object can be instantiated. + """ + key = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + + self.assertEqual(key.cryptographic_algorithm, + CryptographicAlgorithm.AES) + self.assertEqual(key.cryptographic_length, 128) + self.assertEqual(key.value, self.bytes_128a) + self.assertEqual(key.cryptographic_usage_masks, list()) + self.assertEqual(key.names, ['Symmetric Key']) + + def test_init_with_args(self): + """ + Test that a SymmetricKey object can be instantiated with all arguments. + """ + key = SymmetricKey( + CryptographicAlgorithm.AES, + 128, + self.bytes_128a, + masks=[CryptographicUsageMask.ENCRYPT, + CryptographicUsageMask.DECRYPT], + name='Test Symmetric Key') + + self.assertEqual(key.cryptographic_algorithm, + CryptographicAlgorithm.AES) + self.assertEqual(key.cryptographic_length, 128) + self.assertEqual(key.value, self.bytes_128a) + self.assertEqual(key.cryptographic_usage_masks, + [CryptographicUsageMask.ENCRYPT, + CryptographicUsageMask.DECRYPT]) + self.assertEqual(key.names, ['Test Symmetric Key']) + + def test_get_object_type(self): + """ + Test that the object type can be retrieved from the SymmetricKey. + """ + expected = ObjectType.SYMMETRIC_KEY + key = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + observed = key.object_type + + self.assertEqual(expected, observed) + + def test_validate_on_invalid_algorithm(self): + """ + Test that a TypeError is raised when an invalid algorithm value is + used to construct a SymmetricKey. + """ + args = ('invalid', 128, self.bytes_128a) + + self.assertRaises(TypeError, SymmetricKey, *args) + + def test_validate_on_invalid_length(self): + """ + Test that a TypeError is raised when an invalid length value is used + to construct a SymmetricKey. + """ + args = (CryptographicAlgorithm.AES, 'invalid', self.bytes_128a) + + self.assertRaises(TypeError, SymmetricKey, *args) + + def test_validate_on_invalid_value(self): + """ + Test that a TypeError is raised when an invalid value is used to + construct a SymmetricKey. + """ + args = (CryptographicAlgorithm.AES, 128, 0) + + self.assertRaises(TypeError, SymmetricKey, *args) + + def test_validate_on_invalid_masks(self): + """ + Test that a TypeError is raised when an invalid masks value is used to + construct a SymmetricKey. + """ + args = (CryptographicAlgorithm.AES, 128, self.bytes_128a) + kwargs = {'masks': 'invalid'} + + self.assertRaises(TypeError, SymmetricKey, *args, **kwargs) + + def test_validate_on_invalid_mask(self): + """ + Test that a TypeError is raised when an invalid mask value is used to + construct a SymmetricKey. + """ + args = (CryptographicAlgorithm.AES, 128, self.bytes_128a) + kwargs = {'masks': ['invalid']} + + self.assertRaises(TypeError, SymmetricKey, *args, **kwargs) + + def test_validate_on_invalid_name(self): + """ + Test that a TypeError is raised when an invalid name value is used to + construct a SymmetricKey. + """ + args = (CryptographicAlgorithm.AES, 128, self.bytes_128a) + kwargs = {'name': 0} + + self.assertRaises(TypeError, SymmetricKey, *args, **kwargs) + + def test_validate_on_invalid_length_value(self): + """ + Test that a ValueError is raised when an invalid length value is + used to construct a SymmetricKey. + """ + args = (CryptographicAlgorithm.AES, 256, self.bytes_128a) + + self.assertRaises(ValueError, SymmetricKey, *args) + + def test_validate_on_invalid_value_length(self): + """ + Test that a ValueError is raised when an invalid value is used to + construct a SymmetricKey. + """ + args = (CryptographicAlgorithm.AES, 128, self.bytes_256a) + + self.assertRaises(ValueError, SymmetricKey, *args) + + def test_repr(self): + """ + Test that repr can be applied to a SymmetricKey. + """ + key = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + + args = "algorithm={0}, length={1}, value={2}".format( + CryptographicAlgorithm.AES, 128, self.bytes_128a) + expected = "SymmetricKey({0})".format(args) + observed = repr(key) + + self.assertEqual(expected, observed) + + def test_str(self): + """ + Test that str can be applied to a SymmetricKey. + """ + key = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + expected = str(self.bytes_128a) + observed = str(key) + + self.assertEqual(expected, observed) + + def test_equal_on_equal(self): + """ + Test that the equality operator returns True when comparing two + SymmetricKey objects with the same data. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + + self.assertTrue(a == b) + self.assertTrue(b == a) + + def test_equal_on_not_equal_algorithm(self): + """ + Test that the equality operator returns False when comparing two + SymmetricKey objects with different data. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = SymmetricKey(CryptographicAlgorithm.RSA, 128, self.bytes_128a) + + self.assertFalse(a == b) + self.assertFalse(b == a) + + def test_equal_on_not_equal_length(self): + """ + Test that the equality operator returns False when comparing two + SymmetricKey objects with different data. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = SymmetricKey(CryptographicAlgorithm.AES, 256, self.bytes_256a) + b.value = self.bytes_128a + + self.assertFalse(a == b) + self.assertFalse(b == a) + + def test_equal_on_not_equal_value(self): + """ + Test that the equality operator returns False when comparing two + SymmetricKey objects with different data. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128b) + + self.assertFalse(a == b) + self.assertFalse(b == a) + + def test_equal_on_type_mismatch(self): + """ + Test that the equality operator returns False when comparing a + SymmetricKey object to a non-SymmetricKey object. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = "invalid" + + self.assertFalse(a == b) + self.assertFalse(b == a) + + def test_not_equal_on_equal(self): + """ + Test that the inequality operator returns False when comparing + two SymmetricKey objects with the same internal data. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_not_equal_on_not_equal_algorithm(self): + """ + Test that the equality operator returns True when comparing two + SymmetricKey objects with different data. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = SymmetricKey(CryptographicAlgorithm.RSA, 128, self.bytes_128a) + + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_not_equal_on_not_equal_length(self): + """ + Test that the equality operator returns True when comparing two + SymmetricKey objects with different data. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = SymmetricKey(CryptographicAlgorithm.AES, 256, self.bytes_256a) + + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_not_equal_on_not_equal_value(self): + """ + Test that the equality operator returns True when comparing two + SymmetricKey objects with different data. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128b) + + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_not_equal_on_type_mismatch(self): + """ + Test that the equality operator returns True when comparing a + SymmetricKey object to a non-SymmetricKey object. + """ + a = SymmetricKey(CryptographicAlgorithm.AES, 128, self.bytes_128a) + b = "invalid" + + self.assertTrue(a != b) + self.assertTrue(b != a)