Merge pull request #254 from vbnmmnbv/client

Add MAC operation support at client side
This commit is contained in:
Peter Hamilton 2017-02-02 13:09:51 -05:00 committed by GitHub
commit 7fca09fa75
9 changed files with 423 additions and 10 deletions

59
kmip/demos/pie/mac.py Normal file

@ -0,0 +1,59 @@
# Copyright (c) 2017 Pure Storage, Inc. 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
import binascii
from kmip.core import enums
from kmip.demos import utils
from kmip.pie import client
if __name__ == '__main__':
logger = utils.build_console_logger(logging.INFO)
# Build and parse arguments
parser = utils.build_cli_parser(enums.Operation.MAC)
opts, args = parser.parse_args(sys.argv[1:])
config = opts.config
uid = opts.uuid
algorithm = opts.algorithm
data = (
b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E'
b'\x0F')
# Exit early if the arguments are not specified
if uid is None:
logger.error('No UUID provided, exiting early from demo')
sys.exit()
if algorithm is None:
logger.error('No algorithm provided, exiting early from demo')
sys.exit()
algorithm = getattr(enums.CryptographicAlgorithm, algorithm, None)
# Build the client and connect to the server
with client.ProxyKmipClient(config=config) as client:
try:
uid, mac_data = client.mac(uid, algorithm, data)
logger.info("Successfully done MAC using key with ID: "
"{0}".format(uid))
logger.info("MACed data: {0}".format(
str(binascii.hexlify(mac_data))))
except Exception as e:
logger.error(e)

@ -214,15 +214,32 @@ def build_cli_parser(operation=None):
"SECRET_DATA")) "SECRET_DATA"))
elif operation is Operation.DISCOVER_VERSIONS: elif operation is Operation.DISCOVER_VERSIONS:
parser.add_option( parser.add_option(
"-v", "-v",
"--protocol-versions", "--protocol-versions",
action="store", action="store",
type="str", type="str",
default=None, default=None,
dest="protocol_versions", dest="protocol_versions",
help=("Protocol versions supported by client. " help=("Protocol versions supported by client. "
"ex. '1.1,1.2 1.3'")) "ex. '1.1,1.2 1.3'"))
elif operation is Operation.MAC:
parser.add_option(
"-i",
"--uuid",
action="store",
type="str",
default=None,
dest="uuid",
help="The unique ID of the managed object that is the key"
"to use for the MAC operation")
parser.add_option(
"-a",
"--algorithm",
action="store",
type="str",
default=None,
dest="algorithm",
help="Encryption algorithm for the secret (e.g., AES)")
return parser return parser

@ -90,3 +90,17 @@ class KmipClient:
uid (string): The unique ID of the managed object to destroy. uid (string): The unique ID of the managed object to destroy.
""" """
pass pass
@abc.abstractmethod
def mac(self, uid, algorithm, data):
"""
Get the message authentication code for data.
Args:
uid (string): The unique ID of the managed object that is the key
to use for the MAC operation.
algorithm (CryptographicAlgorithm): An enumeration defining the
algorithm to use to generate the MAC.
data (string): The data to be MACed.
"""
pass

@ -21,6 +21,9 @@ from kmip.core import objects as cobjects
from kmip.core.factories import attributes from kmip.core.factories import attributes
from kmip.core.attributes import CryptographicParameters, \
CryptographicAlgorithm
from kmip.pie import api from kmip.pie import api
from kmip.pie import exceptions from kmip.pie import exceptions
from kmip.pie import factory from kmip.pie import factory
@ -476,6 +479,58 @@ class ProxyKmipClient(api.KmipClient):
message = result.result_message.value message = result.result_message.value
raise exceptions.KmipOperationFailure(status, reason, message) raise exceptions.KmipOperationFailure(status, reason, message)
def mac(self, uid, algorithm, data):
"""
Get the message authentication code for data.
Args:
uid (string): The unique ID of the managed object that is the key
to use for the MAC operation.
algorithm (CryptographicAlgorithm): An enumeration defining the
algorithm to use to generate the MAC.
data (string): The data to be MACed.
Returns:
string: The unique ID of the managed object that is the key
to use for the MAC operation.
string: The data MACed
Raises:
ClientConnectionNotOpen: if the client connection is unusable
KmipOperationFailure: if the operation result is a failure
TypeError: if the input arguments are invalid
"""
# Check inputs
if not isinstance(uid, six.string_types):
raise TypeError("uid must be a string")
if not isinstance(algorithm, enums.CryptographicAlgorithm):
raise TypeError(
"algorithm must be a CryptographicAlgorithm enumeration")
if not isinstance(data, six.binary_type):
raise TypeError(
"data must be bytes")
# Verify that operations can be given at this time
if not self._is_open:
raise exceptions.ClientConnectionNotOpen()
parameters_attribute = CryptographicParameters(
cryptographic_algorithm=CryptographicAlgorithm(algorithm))
# Create the symmetric key and handle the results
result = self.proxy.mac(uid, parameters_attribute, data)
status = result.result_status.value
if status == enums.ResultStatus.SUCCESS:
uid = result.uuid.value
mac_data = result.mac_data.value
return uid, mac_data
else:
reason = result.result_reason.value
message = result.result_message.value
raise exceptions.KmipOperationFailure(status, reason, message)
def _build_key_attributes(self, algorithm, length): def _build_key_attributes(self, algorithm, length):
# Build a list of core key attributes. # Build a list of core key attributes.
algorithm_attribute = self.attribute_factory.create_attribute( algorithm_attribute = self.attribute_factory.create_attribute(

@ -27,6 +27,7 @@ from kmip.services.results import QueryResult
from kmip.services.results import RegisterResult from kmip.services.results import RegisterResult
from kmip.services.results import RekeyKeyPairResult from kmip.services.results import RekeyKeyPairResult
from kmip.services.results import RevokeResult from kmip.services.results import RevokeResult
from kmip.services.results import MACResult
from kmip.core import attributes as attr from kmip.core import attributes as attr
@ -60,6 +61,7 @@ from kmip.core.messages.payloads import query
from kmip.core.messages.payloads import rekey_key_pair from kmip.core.messages.payloads import rekey_key_pair
from kmip.core.messages.payloads import register from kmip.core.messages.payloads import register
from kmip.core.messages.payloads import revoke from kmip.core.messages.payloads import revoke
from kmip.core.messages.payloads import mac
from kmip.services.server.kmip_protocol import KMIPProtocol from kmip.services.server.kmip_protocol import KMIPProtocol
@ -428,6 +430,14 @@ class KMIPProxy(KMIP):
results = self._process_batch_items(response) results = self._process_batch_items(response)
return results[0] return results[0]
def mac(self, unique_identifier=None, cryptographic_parameters=None,
data=None, credential=None):
return self._mac(
unique_identifier=unique_identifier,
cryptographic_parameters=cryptographic_parameters,
data=data,
credential=credential)
def _create(self, def _create(self,
object_type=None, object_type=None,
template_attribute=None, template_attribute=None,
@ -919,6 +929,43 @@ class KMIPProxy(KMIP):
uuids) uuids)
return result return result
def _mac(self,
unique_identifier=None,
cryptographic_parameters=None,
data=None,
credential=None):
operation = Operation(OperationEnum.MAC)
req_pl = mac.MACRequestPayload(
unique_identifier=attr.UniqueIdentifier(unique_identifier),
cryptographic_parameters=cryptographic_parameters,
data=objects.Data(data))
batch_item = messages.RequestBatchItem(operation=operation,
request_payload=req_pl)
message = self._build_request_message(credential, [batch_item])
self._send_message(message)
message = messages.ResponseMessage()
data = self._receive_message()
message.read(data)
batch_items = message.batch_items
batch_item = batch_items[0]
payload = batch_item.response_payload
if payload is None:
payload_unique_identifier = None
payload_mac_data = None
else:
payload_unique_identifier = payload.unique_identifier
payload_mac_data = payload.mac_data
result = MACResult(batch_item.result_status,
batch_item.result_reason,
batch_item.result_message,
payload_unique_identifier,
payload_mac_data)
return result
# TODO (peter-hamilton) Augment to handle device credentials # TODO (peter-hamilton) Augment to handle device credentials
def _build_credential(self): def _build_credential(self):
if (self.username is None) and (self.password is None): if (self.username is None) and (self.password is None):
@ -937,7 +984,7 @@ class KMIPProxy(KMIP):
return credential return credential
def _build_request_message(self, credential, batch_items): def _build_request_message(self, credential, batch_items):
protocol_version = ProtocolVersion.create(1, 1) protocol_version = ProtocolVersion.create(1, 2)
if credential is None: if credential is None:
credential = self._build_credential() credential = self._build_credential()

@ -295,3 +295,20 @@ class RevokeResult(OperationResult):
super(RevokeResult, self).__init__( super(RevokeResult, self).__init__(
result_status, result_reason, result_message) result_status, result_reason, result_message)
self.unique_identifier = unique_identifier self.unique_identifier = unique_identifier
class MACResult(OperationResult):
def __init__(self,
result_status,
result_reason=None,
result_message=None,
uuid=None,
mac_data=None):
super(MACResult, self).__init__(
result_status,
result_reason,
result_message
)
self.uuid = uuid
self.mac_data = mac_data

@ -44,6 +44,9 @@ class DummyKmipClient(api.KmipClient):
def destroy(self, uid): def destroy(self, uid):
super(DummyKmipClient, self).destroy(uid) super(DummyKmipClient, self).destroy(uid)
def mac(self, uid, algorithm, data):
super(DummyKmipClient, self).mac(uid, algorithm, data)
class TestKmipClient(testtools.TestCase): class TestKmipClient(testtools.TestCase):
""" """
@ -106,3 +109,10 @@ class TestKmipClient(testtools.TestCase):
""" """
dummy = DummyKmipClient() dummy = DummyKmipClient()
dummy.destroy('uid') dummy.destroy('uid')
def test_mac(self):
"""
Test that the mac method can be called without error.
"""
dummy = DummyKmipClient()
dummy.mac('uid', 'algorithm', 'data')

@ -1056,3 +1056,105 @@ class TestProxyKmipClient(testtools.TestCase):
self.assertIsInstance(opn.attribute_value, attr.OperationPolicyName) self.assertIsInstance(opn.attribute_value, attr.OperationPolicyName)
self.assertEqual(opn.attribute_name.value, 'Operation Policy Name') self.assertEqual(opn.attribute_name.value, 'Operation Policy Name')
self.assertEqual(opn.attribute_value.value, 'test') self.assertEqual(opn.attribute_value.value, 'test')
@mock.patch('kmip.pie.client.KMIPProxy',
mock.MagicMock(spec_set=KMIPProxy))
def test_mac(self):
"""
Test the MAC client with proper input.
"""
uuid = 'aaaaaaaa-1111-2222-3333-ffffffffffff'
algorithm = enums.CryptographicAlgorithm.HMAC_SHA256
data = (b'\x00\x01\x02\x03\x04')
result = results.MACResult(
contents.ResultStatus(enums.ResultStatus.SUCCESS),
uuid=attr.UniqueIdentifier(uuid),
mac_data=obj.MACData(data))
with ProxyKmipClient() as client:
client.proxy.mac.return_value = result
uid, mac_data = client.mac(uuid, algorithm, data)
self.assertEqual(uid, uuid)
self.assertEqual(mac_data, data)
@mock.patch('kmip.pie.client.KMIPProxy',
mock.MagicMock(spec_set=KMIPProxy))
def test_mac_on_invalid_inputs(self):
"""
Test that a TypeError exception is raised when wrong type
of arguments are given to mac operation.
"""
uuid = 'aaaaaaaa-1111-2222-3333-ffffffffffff'
uuid_invalid = int(123)
algorithm = enums.CryptographicAlgorithm.HMAC_SHA256
algorithm_invalid = enums.CryptographicUsageMask.MAC_GENERATE
data = (b'\x00\x01\x02\x03\x04')
data_invalid = int(123)
result = results.MACResult(
contents.ResultStatus(enums.ResultStatus.SUCCESS),
uuid=attr.UniqueIdentifier(uuid),
mac_data=obj.MACData(data))
args = [uuid_invalid, algorithm, data]
with ProxyKmipClient() as client:
client.proxy.mac.return_value = result
self.assertRaises(TypeError, client.mac, *args)
args = [uuid, algorithm_invalid, data]
with ProxyKmipClient() as client:
client.proxy.mac.return_value = result
self.assertRaises(TypeError, client.mac, *args)
args = [uuid, algorithm, data_invalid]
with ProxyKmipClient() as client:
client.proxy.mac.return_value = result
self.assertRaises(TypeError, client.mac, *args)
@mock.patch('kmip.pie.client.KMIPProxy',
mock.MagicMock(spec_set=KMIPProxy))
def test_mac_on_operation_failure(self):
"""
Test that a KmipOperationFailure exception is raised when the
backend fails to generate MAC.
"""
uuid = 'aaaaaaaa-1111-2222-3333-ffffffffffff'
algorithm = enums.CryptographicAlgorithm.HMAC_SHA256
data = (b'\x00\x01\x02\x03\x04')
status = enums.ResultStatus.OPERATION_FAILED
reason = enums.ResultReason.GENERAL_FAILURE
message = "Test failure message"
result = results.OperationResult(
contents.ResultStatus(status),
contents.ResultReason(reason),
contents.ResultMessage(message))
error_msg = str(KmipOperationFailure(status, reason, message))
client = ProxyKmipClient()
client.open()
client.proxy.mac.return_value = result
args = [uuid, algorithm, data]
self.assertRaisesRegexp(
KmipOperationFailure, error_msg, client.mac, *args)
@mock.patch('kmip.pie.client.KMIPProxy',
mock.MagicMock(spec_set=KMIPProxy))
def test_mac_on_closed(self):
"""
Test that a ClientConnectionNotOpen exception is raised when trying
to do mac on an unopened client connection.
"""
client = ProxyKmipClient()
uuid = 'aaaaaaaa-1111-2222-3333-ffffffffffff'
algorithm = enums.CryptographicAlgorithm.HMAC_SHA256
data = (b'\x00\x01\x02\x03\x04')
args = [uuid, algorithm, data]
self.assertRaises(
ClientConnectionNotOpen, client.mac, *args)

@ -16,6 +16,9 @@
from testtools import TestCase from testtools import TestCase
from kmip.core.attributes import PrivateKeyUniqueIdentifier from kmip.core.attributes import PrivateKeyUniqueIdentifier
from kmip.core.attributes import CryptographicParameters, \
CryptographicAlgorithm
from kmip.core.enums import AuthenticationSuite from kmip.core.enums import AuthenticationSuite
from kmip.core.enums import ConformanceClause from kmip.core.enums import ConformanceClause
@ -24,6 +27,8 @@ from kmip.core.enums import ResultStatus as ResultStatusEnum
from kmip.core.enums import ResultReason as ResultReasonEnum from kmip.core.enums import ResultReason as ResultReasonEnum
from kmip.core.enums import Operation as OperationEnum from kmip.core.enums import Operation as OperationEnum
from kmip.core.enums import QueryFunction as QueryFunctionEnum from kmip.core.enums import QueryFunction as QueryFunctionEnum
from kmip.core.enums import CryptographicAlgorithm as \
CryptographicAlgorithmEnum
from kmip.core.factories.attributes import AttributeFactory from kmip.core.factories.attributes import AttributeFactory
from kmip.core.factories.credentials import CredentialFactory from kmip.core.factories.credentials import CredentialFactory
@ -714,6 +719,93 @@ class TestKMIPClient(TestCase):
self.client._create_socket(sock) self.client._create_socket(sock)
self.assertEqual(ssl.SSLSocket, type(self.client.socket)) self.assertEqual(ssl.SSLSocket, type(self.client.socket))
@mock.patch('kmip.services.kmip_client.KMIPProxy._send_message',
mock.MagicMock())
@mock.patch('kmip.services.kmip_client.KMIPProxy._receive_message',
mock.MagicMock())
def test_mac(self):
from kmip.core.utils import BytearrayStream
request_expected = (
b'\x42\x00\x78\x01\x00\x00\x00\xa0\x42\x00\x77\x01\x00\x00\x00\x38'
b'\x42\x00\x69\x01\x00\x00\x00\x20\x42\x00\x6a\x02\x00\x00\x00\x04'
b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x6b\x02\x00\x00\x00\x04'
b'\x00\x00\x00\x02\x00\x00\x00\x00\x42\x00\x0d\x02\x00\x00\x00\x04'
b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x0f\x01\x00\x00\x00\x58'
b'\x42\x00\x5c\x05\x00\x00\x00\x04\x00\x00\x00\x23\x00\x00\x00\x00'
b'\x42\x00\x79\x01\x00\x00\x00\x40\x42\x00\x94\x07\x00\x00\x00\x01'
b'\x31\x00\x00\x00\x00\x00\x00\x00\x42\x00\x2b\x01\x00\x00\x00\x10'
b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x0b\x00\x00\x00\x00'
b'\x42\x00\xc2\x08\x00\x00\x00\x10\x00\x01\x02\x03\x04\x05\x06\x07'
b'\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
response = (
b'\x42\x00\x7b\x01\x00\x00\x00\xd8\x42\x00\x7a\x01\x00\x00\x00\x48'
b'\x42\x00\x69\x01\x00\x00\x00\x20\x42\x00\x6a\x02\x00\x00\x00\x04'
b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x6b\x02\x00\x00\x00\x04'
b'\x00\x00\x00\x02\x00\x00\x00\x00\x42\x00\x92\x09\x00\x00\x00\x08'
b'\x00\x00\x00\x00\x58\x8a\x3f\x23\x42\x00\x0d\x02\x00\x00\x00\x04'
b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x0f\x01\x00\x00\x00\x80'
b'\x42\x00\x5c\x05\x00\x00\x00\x04\x00\x00\x00\x23\x00\x00\x00\x00'
b'\x42\x00\x7f\x05\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x42\x00\x7c\x01\x00\x00\x00\x58\x42\x00\x94\x07\x00\x00\x00\x01'
b'\x31\x00\x00\x00\x00\x00\x00\x00\x42\x00\xc6\x08\x00\x00\x00\x40'
b'\x99\x8b\x55\x59\x90\x9b\x85\x87\x5b\x90\x63\x13\x12\xbb\x32\x9f'
b'\x6a\xc4\xed\x97\x6e\xac\x99\xe5\x21\x53\xc4\x19\x28\xf2\x2a\x5b'
b'\xef\x79\xa4\xbe\x05\x3b\x31\x49\x19\xe0\x75\x23\xb9\xbe\xc8\x23'
b'\x35\x60\x7e\x49\xba\xa9\x7e\xe0\x9e\x6b\x3d\x55\xf4\x51\xff\x7c'
)
response_no_payload = (
b'\x42\x00\x7b\x01\x00\x00\x00\x78\x42\x00\x7a\x01\x00\x00\x00\x48'
b'\x42\x00\x69\x01\x00\x00\x00\x20\x42\x00\x6a\x02\x00\x00\x00\x04'
b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x6b\x02\x00\x00\x00\x04'
b'\x00\x00\x00\x02\x00\x00\x00\x00\x42\x00\x92\x09\x00\x00\x00\x08'
b'\x00\x00\x00\x00\x58\x8a\x3f\x23\x42\x00\x0d\x02\x00\x00\x00\x04'
b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x0f\x01\x00\x00\x00\x80'
b'\x42\x00\x5c\x05\x00\x00\x00\x04\x00\x00\x00\x23\x00\x00\x00\x00'
b'\x42\x00\x7f\x05\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00'
)
data = (b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B'
b'\x0C\x0D\x0E\x0F')
mdata = (b'\x99\x8b\x55\x59\x90\x9b\x85\x87\x5b\x90\x63\x13'
b'\x12\xbb\x32\x9f'
b'\x6a\xc4\xed\x97\x6e\xac\x99\xe5\x21\x53\xc4\x19'
b'\x28\xf2\x2a\x5b'
b'\xef\x79\xa4\xbe\x05\x3b\x31\x49\x19\xe0\x75\x23'
b'\xb9\xbe\xc8\x23'
b'\x35\x60\x7e\x49\xba\xa9\x7e\xe0\x9e\x6b\x3d\x55'
b'\xf4\x51\xff\x7c')
def verify_request(message):
stream = BytearrayStream()
message.write(stream)
self.assertEqual(stream.buffer, request_expected)
uuid = '1'
cryptographic_parameters = CryptographicParameters(
cryptographic_algorithm=CryptographicAlgorithm(
CryptographicAlgorithmEnum.HMAC_SHA512)
)
self.client._send_message.side_effect = verify_request
self.client._receive_message.return_value = BytearrayStream(response)
result = self.client.mac(uuid, cryptographic_parameters,
data)
self.assertEqual(result.uuid.value, uuid)
self.assertEqual(result.mac_data.value, mdata)
self.client._receive_message.return_value = \
BytearrayStream(response_no_payload)
result = self.client.mac(uuid, cryptographic_parameters,
data)
self.assertEqual(result.uuid, None)
self.assertEqual(result.mac_data, None)
class TestClientProfileInformation(TestCase): class TestClientProfileInformation(TestCase):
""" """