Skip to content

Commit a5e9601

Browse files
committed
Add Encrypt support to the clients
This change adds Encrypt operation support to the KMIPProxy and ProxyKmipClient clients, including unit tests to cover the new functionality. Extensive documentation has been added to the header comments for the new client methods detailing the expected input parameters and return values. This approach should be followed for all new client additions going forward.
1 parent 7b8bd47 commit a5e9601

File tree

6 files changed

+482
-0
lines changed

6 files changed

+482
-0
lines changed

kmip/pie/api.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,25 @@ def destroy(self, uid):
139139
"""
140140
pass
141141

142+
@abc.abstractmethod
143+
def encrypt(self, data, uid=None, cryptographic_parameters=None,
144+
iv_counter_nonce=None):
145+
"""
146+
Encrypt data using the specified encryption key and parameters.
147+
148+
Args:
149+
data (bytes): The bytes to encrypt. Required.
150+
uid (string): The unique ID of the encryption key to use.
151+
Optional, defaults to None.
152+
cryptographic_parameters (dict): A dictionary containing various
153+
cryptographic settings to be used for the encryption.
154+
Optional, defaults to None.
155+
iv_counter_nonce (bytes): The bytes to use for the IV/counter/
156+
nonce, if needed by the encryption algorithm and/or cipher
157+
mode. Optional, defaults to None.
158+
"""
159+
pass
160+
142161
@abc.abstractmethod
143162
def mac(self, data, uid, algorithm):
144163
"""

kmip/pie/client.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,142 @@ def destroy(self, uid=None):
649649
message = result.result_message.value
650650
raise exceptions.KmipOperationFailure(status, reason, message)
651651

652+
def encrypt(self, data, uid=None, cryptographic_parameters=None,
653+
iv_counter_nonce=None):
654+
"""
655+
Encrypt data using the specified encryption key and parameters.
656+
657+
Args:
658+
data (bytes): The bytes to encrypt. Required.
659+
uid (string): The unique ID of the encryption key to use.
660+
Optional, defaults to None.
661+
cryptographic_parameters (dict): A dictionary containing various
662+
cryptographic settings to be used for the encryption.
663+
Optional, defaults to None.
664+
iv_counter_nonce (bytes): The bytes to use for the IV/counter/
665+
nonce, if needed by the encryption algorithm and/or cipher
666+
mode. Optional, defaults to None.
667+
668+
Returns:
669+
bytes: The encrypted data.
670+
bytes: The IV/counter/nonce used with the encryption algorithm,
671+
only if it was autogenerated by the server.
672+
673+
Raises:
674+
ClientConnectionNotOpen: if the client connection is unusable
675+
KmipOperationFailure: if the operation result is a failure
676+
TypeError: if the input arguments are invalid
677+
678+
Notes:
679+
The cryptographic_parameters argument is a dictionary that can
680+
contain the following key/value pairs:
681+
682+
Keys | Value
683+
------------------------------|-----------------------------------
684+
'block_cipher_mode' | A BlockCipherMode enumeration
685+
| indicating the cipher mode to use
686+
| with the encryption algorithm.
687+
'padding_method' | A PaddingMethod enumeration
688+
| indicating which padding method to
689+
| use with the encryption algorithm.
690+
'hashing_algorithm' | A HashingAlgorithm enumeration
691+
| indicating which hashing algorithm
692+
| to use.
693+
'key_role_type' | A KeyRoleType enumeration
694+
| indicating the intended use of the
695+
| associated cryptographic key.
696+
'digital_signature_algorithm' | A DigitalSignatureAlgorithm
697+
| enumeration indicating which
698+
| digital signature algorithm to
699+
| use.
700+
'cryptographic_algorithm' | A CryptographicAlgorithm
701+
| enumeration indicating which
702+
| encryption algorithm to use.
703+
'random_iv' | A boolean indicating whether the
704+
| server should autogenerate an IV.
705+
'iv_length' | An integer representing the length
706+
| of the initialization vector (IV)
707+
| in bits.
708+
'tag_length' | An integer representing the length
709+
| of the authenticator tag in bytes.
710+
'fixed_field_length' | An integer representing the length
711+
| of the fixed field portion of the
712+
| IV in bits.
713+
'invocation_field_length' | An integer representing the length
714+
| of the invocation field portion of
715+
| the IV in bits.
716+
'counter_length' | An integer representing the length
717+
| of the coutner portion of the IV
718+
| in bits.
719+
'initial_counter_value' | An integer representing the
720+
| starting counter value for CTR
721+
| mode (typically 1).
722+
"""
723+
# Check input
724+
if not isinstance(data, six.binary_type):
725+
raise TypeError("data must be bytes")
726+
if uid is not None:
727+
if not isinstance(uid, six.string_types):
728+
raise TypeError("uid must be a string")
729+
if cryptographic_parameters is not None:
730+
if not isinstance(cryptographic_parameters, dict):
731+
raise TypeError("cryptographic_parameters must be a dict")
732+
if iv_counter_nonce is not None:
733+
if not isinstance(iv_counter_nonce, six.binary_type):
734+
raise TypeError("iv_counter_nonce must be bytes")
735+
736+
# Verify that operations can be given at this time
737+
if not self._is_open:
738+
raise exceptions.ClientConnectionNotOpen()
739+
740+
cryptographic_parameters = CryptographicParameters(
741+
block_cipher_mode=cryptographic_parameters.get(
742+
'block_cipher_mode'
743+
),
744+
padding_method=cryptographic_parameters.get('padding_method'),
745+
hashing_algorithm=cryptographic_parameters.get(
746+
'hashing_algorithm'
747+
),
748+
key_role_type=cryptographic_parameters.get('key_role_type'),
749+
digital_signature_algorithm=cryptographic_parameters.get(
750+
'digital_signature_algorithm'
751+
),
752+
cryptographic_algorithm=cryptographic_parameters.get(
753+
'cryptographic_algorithm'
754+
),
755+
random_iv=cryptographic_parameters.get('random_iv'),
756+
iv_length=cryptographic_parameters.get('iv_length'),
757+
tag_length=cryptographic_parameters.get('tag_length'),
758+
fixed_field_length=cryptographic_parameters.get(
759+
'fixed_field_length'
760+
),
761+
invocation_field_length=cryptographic_parameters.get(
762+
'invocation_field_length'
763+
),
764+
counter_length=cryptographic_parameters.get('counter_length'),
765+
initial_counter_value=cryptographic_parameters.get(
766+
'initial_counter_value'
767+
)
768+
)
769+
770+
# Encrypt the provided data and handle the results
771+
result = self.proxy.encrypt(
772+
data,
773+
uid,
774+
cryptographic_parameters,
775+
iv_counter_nonce
776+
)
777+
778+
status = result.get('result_status')
779+
if status == enums.ResultStatus.SUCCESS:
780+
return result.get('data'), result.get('iv_counter_nonce')
781+
else:
782+
raise exceptions.KmipOperationFailure(
783+
status,
784+
result.get('result_reason'),
785+
result.get('result_message')
786+
)
787+
652788
def mac(self, data, uid=None, algorithm=None):
653789
"""
654790
Get the message authentication code for data.

kmip/services/kmip_client.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from kmip.core.messages.payloads import create_key_pair
5454
from kmip.core.messages.payloads import destroy
5555
from kmip.core.messages.payloads import discover_versions
56+
from kmip.core.messages.payloads import encrypt
5657
from kmip.core.messages.payloads import get
5758
from kmip.core.messages.payloads import get_attributes
5859
from kmip.core.messages.payloads import get_attribute_list
@@ -433,6 +434,78 @@ def discover_versions(self, batch=False, protocol_versions=None,
433434
results = self._process_batch_items(response)
434435
return results[0]
435436

437+
def encrypt(self,
438+
data,
439+
unique_identifier=None,
440+
cryptographic_parameters=None,
441+
iv_counter_nonce=None,
442+
credential=None):
443+
"""
444+
Encrypt data using the specified encryption key and parameters.
445+
446+
Args:
447+
data (bytes): The bytes to encrypt. Required.
448+
unique_identifier (string): The unique ID of the encryption key
449+
to use. Optional, defaults to None.
450+
cryptographic_parameters (CryptographicParameters): A structure
451+
containing various cryptographic settings to be used for the
452+
encryption. Optional, defaults to None.
453+
iv_counter_nonce (bytes): The bytes to use for the IV/counter/
454+
nonce, if needed by the encryption algorithm and/or cipher
455+
mode. Optional, defaults to None.
456+
credential (Credential): A credential object containing a set of
457+
authorization parameters for the operation. Optional, defaults
458+
to None.
459+
460+
Returns:
461+
dict: The results of the encrypt operation, containing the
462+
following key/value pairs:
463+
464+
Key | Value
465+
--------------------|-----------------------------------------
466+
'unique_identifier' | (string) The unique ID of the encryption
467+
| key used to encrypt the data.
468+
'data' | (bytes) The encrypted data.
469+
'iv_counter_nonce' | (bytes) The IV/counter/nonce used for
470+
| the encryption, if autogenerated.
471+
'result_status' | (ResultStatus) An enumeration indicating
472+
| the status of the operation result.
473+
'result_reason' | (ResultReason) An enumeration providing
474+
| context for the result status.
475+
'result_message' | (string) A message providing additional
476+
| context for the operation result.
477+
"""
478+
operation = Operation(OperationEnum.ENCRYPT)
479+
480+
request_payload = encrypt.EncryptRequestPayload(
481+
unique_identifier=unique_identifier,
482+
data=data,
483+
cryptographic_parameters=cryptographic_parameters,
484+
iv_counter_nonce=iv_counter_nonce
485+
)
486+
batch_item = messages.RequestBatchItem(
487+
operation=operation,
488+
request_payload=request_payload
489+
)
490+
491+
request = self._build_request_message(credential, [batch_item])
492+
response = self._send_and_receive_message(request)
493+
batch_item = response.batch_items[0]
494+
payload = batch_item.response_payload
495+
496+
result = {}
497+
498+
if payload:
499+
result['unique_identifier'] = payload.unique_identifier
500+
result['data'] = payload.data
501+
result['iv_counter_nonce'] = payload.iv_counter_nonce
502+
503+
result['result_status'] = batch_item.result_status
504+
result['result_reason'] = batch_item.result_reason
505+
result['result_message'] = batch_item.result_message
506+
507+
return result
508+
436509
def mac(self, data, unique_identifier=None,
437510
cryptographic_parameters=None, credential=None):
438511
return self._mac(

kmip/tests/unit/pie/test_api.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ def revoke(self, revocation_reason, uid, revocation_message,
5959
def destroy(self, uid):
6060
super(DummyKmipClient, self).destroy(uid)
6161

62+
def encrypt(self,
63+
data,
64+
uid=None,
65+
cryptographic_parameters=None,
66+
iv_counter_nonce=None):
67+
super(DummyKmipClient, self).encrypt(
68+
data,
69+
uid,
70+
cryptographic_parameters,
71+
iv_counter_nonce
72+
)
73+
6274
def mac(self, data, uid, algorithm):
6375
super(DummyKmipClient, self).mac(data, uid, algorithm)
6476

@@ -147,6 +159,14 @@ def test_destroy(self):
147159
dummy = DummyKmipClient()
148160
dummy.destroy('uid')
149161

162+
def test_encrypt(self):
163+
"""
164+
Test that the encrypt method can be called without error.
165+
:return:
166+
"""
167+
dummy = DummyKmipClient()
168+
dummy.encrypt('data', 'uid', 'crypto_params', 'iv')
169+
150170
def test_mac(self):
151171
"""
152172
Test that the mac method can be called without error.

0 commit comments

Comments
 (0)