From 195671d4bfb70384b905d8694eb2655151c0be80 Mon Sep 17 00:00:00 2001 From: Peter Hamilton Date: Tue, 5 May 2015 14:55:34 -0400 Subject: [PATCH] Adding support for the Certificate managed object This change polishes and reorganizes the implementation of the Certificate managed object and its required subclasses. It adds in documentation and test suites for all modified classes and updates the SecretFactory to support creating default Certificate objects. --- kmip/core/attributes.py | 25 +- kmip/core/enums.py | 7 +- kmip/core/factories/secrets.py | 7 +- kmip/core/misc.py | 22 + kmip/core/primitives.py | 3 +- kmip/core/secrets.py | 90 ++++- kmip/tests/core/attributes/test_attributes.py | 46 +++ kmip/tests/core/misc/test_misc.py | 46 +++ kmip/tests/core/secrets/__init__.py | 14 + kmip/tests/core/secrets/test_certificate.py | 378 ++++++++++++++++++ 10 files changed, 607 insertions(+), 31 deletions(-) create mode 100644 kmip/tests/core/secrets/__init__.py create mode 100644 kmip/tests/core/secrets/test_certificate.py diff --git a/kmip/core/attributes.py b/kmip/core/attributes.py index 31b722f..0f0407b 100644 --- a/kmip/core/attributes.py +++ b/kmip/core/attributes.py @@ -15,6 +15,7 @@ from kmip.core import enums +from kmip.core.enums import CertificateTypeEnum from kmip.core.enums import HashingAlgorithm as HashingAlgorithmEnum from kmip.core.enums import KeyFormatType as KeyFormatTypeEnum from kmip.core.enums import Tags @@ -283,16 +284,28 @@ class CryptographicParameters(Struct): pass -# 3.8 class CertificateType(Enumeration): - ENUM_TYPE = enums.CertificateType + """ + An encodeable wrapper for the CertificateType enumeration. - def __init__(self, value=None): - super(self.__class__, self).__init__(value, - Tags.CERTIFICATE_TYPE) + Used to specify the type of the encoded bytes of a Certificate Managed + Object. See Sections 2.2.1 and 3.8 of the KMIP v1.1 specification for more + information. + """ + ENUM_TYPE = enums.CertificateTypeEnum + + def __init__(self, value=CertificateTypeEnum.X_509): + """ + Construct a CertificateType object. + + Args: + value (CertificateTypeEnum): A CertificateTypeEnum enumeration + value, (e.g., CertificateTypeEnum.PGP). Optional, defaults to + CertificateTypeEnum.X_509. + """ + super(CertificateType, self).__init__(value, Tags.CERTIFICATE_TYPE) -# 3.17 class DigestValue(ByteString): """ A byte string representing the hash value of a Digest. diff --git a/kmip/core/enums.py b/kmip/core/enums.py index d7ba630..2aee2d1 100644 --- a/kmip/core/enums.py +++ b/kmip/core/enums.py @@ -349,7 +349,12 @@ class WrappingMethod(Enum): # 9.1.3.2.6 -class CertificateType(Enum): +class CertificateTypeEnum(Enum): + """ + The type of a Certificate Managed Object. + + For more information, see Section 2.2.1 of the KMIP 1.1 specification. + """ X_509 = 0x00000001 PGP = 0x00000002 diff --git a/kmip/core/factories/secrets.py b/kmip/core/factories/secrets.py index 796ebce..394df81 100644 --- a/kmip/core/factories/secrets.py +++ b/kmip/core/factories/secrets.py @@ -28,6 +28,7 @@ from kmip.core.objects import KeyMaterial from kmip.core.objects import KeyWrappingData from kmip.core.objects import KeyValue +from kmip.core.secrets import Certificate from kmip.core.secrets import OpaqueObject from kmip.core.secrets import PrivateKey from kmip.core.secrets import PublicKey @@ -68,7 +69,7 @@ class SecretFactory(object): SymmetricKey(...) """ if secret_type is ObjectType.CERTIFICATE: - return self._create_certificate(value) + return self._create_certificate() elif secret_type is ObjectType.SYMMETRIC_KEY: return self._create_symmetric_key(value) elif secret_type is ObjectType.PUBLIC_KEY: @@ -87,8 +88,8 @@ class SecretFactory(object): raise TypeError("Unrecognized secret type: {0}".format( secret_type)) - def _create_certificate(self, value): - raise NotImplementedError() + def _create_certificate(self): + return Certificate() def _create_symmetric_key(self, value): if value is None: diff --git a/kmip/core/misc.py b/kmip/core/misc.py index 106c60f..05b4355 100644 --- a/kmip/core/misc.py +++ b/kmip/core/misc.py @@ -17,6 +17,7 @@ from kmip.core.enums import KeyFormatType as KeyFormatTypeEnum from kmip.core.enums import Tags from kmip.core.enums import QueryFunction as QueryFunctionEnum +from kmip.core.primitives import ByteString from kmip.core.primitives import Enumeration from kmip.core.primitives import Interval from kmip.core.primitives import Struct @@ -25,6 +26,27 @@ from kmip.core.primitives import TextString from kmip.core.utils import BytearrayStream +class CertificateValue(ByteString): + """ + The bytes of a DER-encoded X.509 public key certificate. + + Used by the Certificate Managed Object to store the bytes of the + certificate. See Section 2.2.1 of the KMIP 1.1. specification for more + information. + """ + + def __init__(self, value=b''): + """ + Construct a CertificateValue byte string. + + Args: + value (bytes): A byte string (e.g., b'\x00\x01...') containing the + certificate bytes to store. Optional, defaults to the empty + byte string. + """ + super(CertificateValue, self).__init__(value, Tags.CERTIFICATE_VALUE) + + class Offset(Interval): """ An integer representing a positive change in time. diff --git a/kmip/core/primitives.py b/kmip/core/primitives.py index d22ec02..cbe39be 100644 --- a/kmip/core/primitives.py +++ b/kmip/core/primitives.py @@ -420,8 +420,7 @@ class Enumeration(Integer): return "{0}(value={1})".format(type(self).__name__, self.enum) def __str__(self): - return "{0} - {1} - {2}".format( - type(self.enum), self.enum.name, self.enum.value) + return "{0}.{1}".format(type(self.enum).__name__, self.enum.name) class Boolean(Base): diff --git a/kmip/core/secrets.py b/kmip/core/secrets.py index 08b080d..5347e04 100644 --- a/kmip/core/secrets.py +++ b/kmip/core/secrets.py @@ -18,6 +18,8 @@ from kmip.core.attributes import CertificateType from kmip.core import enums from kmip.core.enums import Tags +from kmip.core.misc import CertificateValue + from kmip.core.objects import Attribute from kmip.core.objects import KeyBlock @@ -33,52 +35,102 @@ from kmip.core.utils import BytearrayStream # 2.2 # 2.2.1 class Certificate(Struct): + """ + A structure representing a DER-encoded X.509 public key certificate. - class CertificateValue(ByteString): + See Section 2.2.1 of the KMIP 1.1 specification for more information. - def __init__(self, value=None): - super(self.__class__, self).__init__(value, - Tags.CERTIFICATE_VALUE) + Attributes: + certificate_type: The type of the certificate. + certificate_value: The bytes of the certificate. + """ def __init__(self, certificate_type=None, certificate_value=None): - super(self.__class__, self).__init__(Tags.CERTIFICATE) - self.certificate_type = certificate_type - self.certificate_value = certificate_value - self.validate() + """ + Construct a Certificate object. + + Args: + certificate_type (CertificateTypeEnum): The type of the + certificate. Optional, defaults to None. + certificate_value (bytes): The bytes of the certificate. Optional, + defaults to None. + """ + super(Certificate, self).__init__(Tags.CERTIFICATE) + + if certificate_type is None: + self.certificate_type = CertificateType() + else: + self.certificate_type = CertificateType(certificate_type) + + if certificate_value is None: + self.certificate_value = CertificateValue() + else: + self.certificate_value = CertificateValue(certificate_value) def read(self, istream): - super(self.__class__, self).read(istream) + """ + Read the data encoding the Certificate object and decode it into its + constituent parts. + + Args: + istream (Stream): A data stream containing encoded object data, + supporting a read method; usually a BytearrayStream object. + """ + super(Certificate, self).read(istream) tstream = BytearrayStream(istream.read(self.length)) self.certificate_type = CertificateType() - self.certificate_value = Certificate.CertificateValue() + self.certificate_value = CertificateValue() self.certificate_type.read(tstream) self.certificate_value.read(tstream) self.is_oversized(tstream) - self.validate() def write(self, ostream): + """ + Write the data encoding the Certificate object to a stream. + + Args: + ostream (Stream): A data stream in which to encode object data, + supporting a write method; usually a BytearrayStream object. + """ tstream = BytearrayStream() - # Write the details of the certificate self.certificate_type.write(tstream) self.certificate_value.write(tstream) - # Write the length and value of the template attribute self.length = tstream.length() - super(self.__class__, self).write(ostream) + super(Certificate, self).write(ostream) ostream.write(tstream.buffer) - def validate(self): - self.__validate() + def __eq__(self, other): + if isinstance(other, Certificate): + if self.certificate_type != other.certificate_type: + return False + elif self.certificate_value != other.certificate_value: + return False + else: + return True + else: + return NotImplemented - def __validate(self): - # TODO (peter-hamilton) Finish implementation. - pass + def __ne__(self, other): + if isinstance(other, Certificate): + return not (self == other) + else: + return NotImplemented + + def __repr__(self): + return "{0}(certificate_type={1}, certificate_value=b'{2}')".format( + type(self).__name__, + str(self.certificate_type), + str(self.certificate_value)) + + def __str__(self): + return "{0}".format(str(self.certificate_value)) # 2.2.2 diff --git a/kmip/tests/core/attributes/test_attributes.py b/kmip/tests/core/attributes/test_attributes.py index d598c89..9cbc080 100644 --- a/kmip/tests/core/attributes/test_attributes.py +++ b/kmip/tests/core/attributes/test_attributes.py @@ -17,10 +17,12 @@ 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 DigestValue from kmip.core.attributes import HashingAlgorithm from kmip.core.attributes import OperationPolicyName +from kmip.core.enums import CertificateTypeEnum from kmip.core.enums import HashingAlgorithm as HashingAlgorithmEnum from kmip.core.utils import BytearrayStream @@ -140,6 +142,50 @@ class TestHashingAlgorithm(TestCase): self._test_init("invalid") +# TODO (peter-hamilton) Replace with generic Enumeration subclass test suite. +class TestCertificateType(TestCase): + """ + A test suite for the CertificateType class. + + Since CertificateType is a simple wrapper for the Enumeration primitive, + only a few tests pertaining to construction are needed. + """ + + def setUp(self): + super(TestCertificateType, self).setUp() + + def tearDown(self): + super(TestCertificateType, self).tearDown() + + def _test_init(self, value): + if (isinstance(value, CertificateTypeEnum)) or (value is None): + if value is None: + certificate_type = CertificateType() + value = CertificateTypeEnum.X_509 + else: + certificate_type = CertificateType(value) + + msg = "expected {0}, observed {1}".format( + value, certificate_type.enum) + self.assertEqual(value, certificate_type.enum, msg) + else: + self.assertRaises(TypeError, CertificateType, value) + + def test_init_with_none(self): + """ + Test that a CertificateType object can be constructed with no specified + value. + """ + self._test_init(None) + + def test_init_with_valid(self): + """ + Test that a CertificateType object can be constructed with valid byte + data. + """ + self._test_init(CertificateTypeEnum.PGP) + + class TestDigestValue(TestCase): """ A test suite for the DigestValue class. diff --git a/kmip/tests/core/misc/test_misc.py b/kmip/tests/core/misc/test_misc.py index ad7f01c..6becab3 100644 --- a/kmip/tests/core/misc/test_misc.py +++ b/kmip/tests/core/misc/test_misc.py @@ -13,17 +13,63 @@ # License for the specific language governing permissions and limitations # under the License. +from six import binary_type from six import string_types + from testtools import TestCase from kmip.core.enums import KeyFormatType as KeyFormatTypeEnum from kmip.core.enums import QueryFunction as QueryFunctionEnum +from kmip.core.misc import CertificateValue from kmip.core.misc import KeyFormatType from kmip.core.misc import QueryFunction from kmip.core.misc import VendorIdentification +# TODO (peter-hamilton) Replace with generic ByteString subclass test suite. +class TestCertificateValue(TestCase): + """ + A test suite for the CertificateValue class. + + Since CertificateValue is a simple wrapper for the ByteString primitive, + only a few tests pertaining to construction are needed. + """ + + def setUp(self): + super(TestCertificateValue, self).setUp() + + def tearDown(self): + super(TestCertificateValue, self).tearDown() + + def _test_init(self, value): + if (isinstance(value, binary_type)) or (value is None): + certificate_value = CertificateValue(value) + + if value is None: + value = b'' + + msg = "expected {0}, observed {1}".format( + value, certificate_value.value) + self.assertEqual(value, certificate_value.value, msg) + else: + self.assertRaises(TypeError, CertificateValue, value) + + def test_init_with_none(self): + """ + Test that a CertificateValue object can be constructed with no + specified value. + """ + self._test_init(None) + + def test_init_with_valid(self): + """ + Test that a CertificateValue object can be constructed with a valid, + byte-string value. + """ + self._test_init(b'\x00\x01\x02') + + class TestQueryFunction(TestCase): """ A test suite for the QueryFunction class. diff --git a/kmip/tests/core/secrets/__init__.py b/kmip/tests/core/secrets/__init__.py new file mode 100644 index 0000000..417e2f9 --- /dev/null +++ b/kmip/tests/core/secrets/__init__.py @@ -0,0 +1,14 @@ +# 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. diff --git a/kmip/tests/core/secrets/test_certificate.py b/kmip/tests/core/secrets/test_certificate.py new file mode 100644 index 0000000..fe261f1 --- /dev/null +++ b/kmip/tests/core/secrets/test_certificate.py @@ -0,0 +1,378 @@ +# 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.attributes import CertificateType +from kmip.core.enums import CertificateTypeEnum +from kmip.core.misc import CertificateValue +from kmip.core.secrets import Certificate +from kmip.core.utils import BytearrayStream + + +class TestCertificate(TestCase): + """ + A test suite for the Certificate class. + """ + + def setUp(self): + super(TestCertificate, self).setUp() + + self.certificate_type_a = None + self.certificate_type_b = CertificateTypeEnum.X_509 + + # Encodings obtained from Section 13.2 of KMIP 1.1 Test Cases document. + self.certificate_value_a = None + self.certificate_value_b = ( + 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') + + self.encoding_a = BytearrayStream(( + b'\x42\x00\x13\x01\x00\x00\x00\x18\x42\x00\x1D\x05\x00\x00\x00\x04' + b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x1E\x08\x00\x00\x00' + b'\x00')) + self.encoding_b = BytearrayStream(( + b'\x42\x00\x13\x01\x00\x00\x03\x30\x42\x00\x1D\x05\x00\x00\x00\x04' + b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x1E\x08\x00\x00\x03\x16' + 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\x00\x00')) + + def tearDown(self): + super(TestCertificate, self).tearDown() + + def test_init(self): + pass + + def _test_read(self, stream, certificate_type, certificate_value): + certificate = Certificate() + certificate.read(stream) + + if certificate_type is None: + expected = CertificateType() + else: + expected = CertificateType(certificate_type) + + observed = certificate.certificate_type + + msg = "certificate type encoding mismatch; " + msg += "expected {0}, observed {1}".format(expected, observed) + self.assertEqual(expected, observed) + + if certificate_value is None: + expected = CertificateValue() + else: + expected = CertificateValue(certificate_value) + + observed = certificate.certificate_value + + msg = "certificate value encoding mismatch; " + msg += "expected {0}, observed {1}".format(expected, observed) + self.assertEqual(expected, observed, msg) + + def test_read_a(self): + """ + Test that a Certificate object with no data can be read from a data + stream. + """ + self._test_read(self.encoding_a, self.certificate_type_a, + self.certificate_value_a) + + def test_read_b(self): + """ + Test that a Certificate object with data can be read from a data + stream. + """ + self._test_read(self.encoding_b, self.certificate_type_b, + self.certificate_value_b) + + def _test_write(self, stream, certificate_type, certificate_value): + certificate = Certificate( + certificate_type=certificate_type, + certificate_value=certificate_value) + + expected = stream + observed = BytearrayStream() + + certificate.write(observed) + + msg = "encoding mismatch;\nexpected:\n{0}\nobserved:\n{1}".format( + expected, observed) + self.assertEqual(expected, observed, msg) + + def test_write_a(self): + """ + Test that a Certificate object with no data can be written to a data + stream. + """ + self._test_write(self.encoding_a, self.certificate_type_a, + self.certificate_value_a) + + def test_write_b(self): + """ + Test that a Certificate object with data can be written to a data + stream. + """ + self._test_write(self.encoding_b, self.certificate_type_b, + self.certificate_value_b) + + def test_equal_on_equal(self): + """ + Test that the equality operator returns True when comparing two + Certificate objects with the same internal data. + """ + a = Certificate( + certificate_type=self.certificate_type_b, + certificate_value=self.certificate_value_b) + b = Certificate( + certificate_type=self.certificate_type_b, + certificate_value=self.certificate_value_b) + + self.assertTrue(a == b) + self.assertTrue(b == a) + + def test_equal_on_equal_and_empty(self): + """ + Test that the equality operator returns True when comparing two + Certificate objects with no internal data. + """ + a = Certificate( + certificate_type=self.certificate_type_a, + certificate_value=self.certificate_value_a) + b = Certificate( + certificate_type=self.certificate_type_a, + certificate_value=self.certificate_value_a) + + self.assertTrue(a == b) + self.assertTrue(b == a) + + def test_equal_on_not_equal(self): + """ + Test that the equality operator returns False when comparing two + Certificate objects with different sets of internal data. + """ + a = Certificate( + certificate_type=self.certificate_type_a, + certificate_value=self.certificate_value_a) + b = Certificate( + certificate_type=self.certificate_type_b, + certificate_value=self.certificate_value_b) + + 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 + Certificate object with a non-Certificate object. + """ + a = Certificate( + certificate_type=self.certificate_type_a, + certificate_value=self.certificate_value_a) + 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 + Certificate objects with the same internal data. + """ + a = Certificate( + certificate_type=self.certificate_type_b, + certificate_value=self.certificate_value_b) + b = Certificate( + certificate_type=self.certificate_type_b, + certificate_value=self.certificate_value_b) + + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_not_equal_on_equal_and_empty(self): + """ + Test that the inequality operator returns False when comparing two + Certificate objects with no internal data. + """ + a = Certificate( + certificate_type=self.certificate_type_a, + certificate_value=self.certificate_value_a) + b = Certificate( + certificate_type=self.certificate_type_a, + certificate_value=self.certificate_value_a) + + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_not_equal_on_not_equal(self): + """ + Test that the inequality operator returns True when comparing two + Certificate objects with different sets of internal data. + """ + a = Certificate( + certificate_type=self.certificate_type_a, + certificate_value=self.certificate_value_a) + b = Certificate( + certificate_type=self.certificate_type_b, + certificate_value=self.certificate_value_b) + + 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 a + Certificate object with a non-Certificate object. + """ + a = Certificate( + certificate_type=self.certificate_type_a, + certificate_value=self.certificate_value_a) + b = "invalid" + + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_repr(self): + """ + Test that the representation of a Certificate object with data is + formatted properly. + """ + certificate = Certificate( + certificate_type=self.certificate_type_b, + certificate_value=self.certificate_value_b) + + certificate_type = "certificate_type={0}".format( + str(self.certificate_type_b)) + certificate_value = "certificate_value=b'{0}'".format( + str(self.certificate_value_b)) + + expected = "Certificate({0}, {1})".format( + certificate_type, certificate_value) + observed = repr(certificate) + + msg = "\nexpected:\n{0}\nobserved:\n{1}".format(expected, observed) + self.assertEqual(expected, observed, msg) + + # NOTE (peter-hamilton) Testing with eval won't work due to null bytes. + + def test_str(self): + """ + Test that the string representation of a Certificate object is + formatted properly. + """ + certificate = Certificate( + certificate_type=self.certificate_type_b, + certificate_value=self.certificate_value_b) + + expected = str(self.certificate_value_b) + observed = str(certificate) + + msg = "expected:\n{0}\nobserved:\n{1}".format(expected, observed) + self.assertEqual(expected, observed, msg)