MTG KMIP SDK Business Processes

Getting started

The following classes are available from the MTG KMIP SDK in order to build java client applications and communicate with the MTG KMS-Server.

An example of how to use these classes is described below, and additionally, an example as source code is available inside the example directory Example Source Code.

KmipClient

This class handles the communication with the MTG KMS-Server.

RequestBuilder

This class can be used to build batch requests by combining multiple batch items. KMIP-SPEC, 8.1

RequestBatchItem

This class encapsulates one item for a KMIP operation KMIP-SPEC, 8.3.

RequestPayload

This is the base class for the request payload. There are specific payload classes for the different KMIP operations KMIP-SPEC, 6.1.

The operation specific classes follow the naming convention RequestPayload<operation>.

ResponseBatchItem

This class encapsulates one response item for a KMIP operation KMIP-SPEC, 8.6.

ResponsePayload

This is the base class for the response payload. There are specific payload classes for the different KMIP operations KMIP-SPEC, 6.1.

The operation specific classes follow the naming convention ResponsePayload<operation>.

For all classes, there are builder classes available. This way, only the necessary properties need to be set, and the build() method will take care of everything.

KmipClient

The KmipClient is the interface that connects to the MTG KMS-Server and handles the request and response preprocessing and marshaling.

Client Setup

The easiest way to create a KmipClient is to use the KmipClientBuilder. By doing that, only the necessary properties need to be set, and the KmipClientBuilder.build() method will take care of everything else.

KmipClientBuilder clientBuilder = new KmipClientBuilder()

The most important property to set is the base URL of the MTG KMS-Server.

clientBuilder.setBaseUrl("http:://<server>")

If HTTPS is used, a TrustManager must be given. See the provided source code example on how to create one.

clientBuilder.trustManagerFactory(trustManager)

For client authentication, two options are available. Either via a certificate or via Basic-Auth.

If the KMS client is configured to have a Client-Auth using a certificate, the KeyManager must be given. See the provided source code example on how to create one.

clientBuilder.keyManagerFactory(keyManager)

If the KMS Client is configured to use Basic-Auth, the correct corresponding filter with the client’s username and password must be provided.

clientBuilder.filter(ExchangeFilterFunctions.basicAuthentication("username", "password");

Additional ExchangeFilterFunctions can be added. For example, a filter for the handling of HTTP errors.

clientBuilder.filter(ExchangeFilterFunctions.statusError(HttpStatus::isError, req -> new KmipCliException("Failed to communicate with the KMS. " + req.statusCode() )));

It is possible to enforce policies by extending the KmipPolicy interface.
The BatchErrorContinuationOptionPolicy demonstrates how to enforce a specific batch error continuation option KMIP-SPEC 11.5 for the client’s messages.

clientBuilder.policy(new BatchErrorContinuationOptionPolicy(BatchErrorContinuationOption.CONTINUE))

Sending of Requests

The KmipClient offers synchronous and asynchronous methods for sending requests.
All synchronous methods start with send and directly return the responses, while all asynchronous methods start with execute and return Mono objects that wrap the responses to enable asynchronous communication.

Synchronous Methods
  • KmipClient.sendSingle

  • KmipClient.sendSingleWithContext

  • KmipClient.send

  • KmipClient.sendWithContext

Asynchronous Methods
  • KmipClient.executeSingle

  • KmipClient.executeSingleWithContext

  • KmipClient.execute

  • KmipClient.executeWithContext

The suffixed WithContext methods can be used to provide a KmipContext with additional data to KmipPolicy classes to modify their behavior on a per-call basis.

Example Batch Operations

To demonstrate the usage, we assume the user wants to create a key in the KMS and activate this key immediately. This means we need two KMIP operations, Create and Activate, which can be combined into one batch request.

First, we create the payload for the Create operation.

        Attributes attributes = new AttributesBuilder()
                .addAttributes(new Name(name, NameType.UNINTERPRETED_TEXT_STRING))
                .addAttributes(CryptographicAlgorithm.AES)
                .addAttributes(new SimpleItemWrapper<>(cryptographicLength, Tag.CRYPTOGRAPHIC_LENGTH, ItemType.INTEGER))
                .addAttributes(CryptographicUsageMasksVirtual.of(
                        CryptographicUsageMask.DECRYPT,
                        CryptographicUsageMask.ENCRYPT
                ))
                .build();

        return new RequestPayloadCreateBuilder()
                .setObjectType(ObjectType.SYMMETRIC_KEY)
                .setAttributes(attributes)
                .build();

Next, we need the payload for the Activate operation. Here we tell the KMS-Server to use the ID of the Create operation.

It is possible to rely on the KMS-Server’s ID Placeholder mechanism described in KMIP-SPEC 6.1 for batched operations on the created managed object. Therefore, the unique identifier field in the RequestPayloadActivate can also be empty in this example.
RequestPayload payloadActivate = new RequestPayloadActivateBuilder()
        .setUniqueIdentifier(new UniqueIdentifierEnumeration(UniqueIdentifier.CREATE))
        .build();

To combine the payloads into a single request message, we can use the RequestBuilder. The builder will create the batch request items for the operations for us.

RequestBuilder requestBuilder = new RequestBuilder();
UniqueBatchItemId idCreate = requestBuilder.addPayload(payloadCreate);
UniqueBatchItemId idActivate = requestBuilder.addPayload(payloadActivate);

When adding a new payload to the RequestBuilder, we receive a UniqueBatchItemId that we can later use to match the responses to the request payloads.

To enable communication to the KMS-Server, we need to initialize a KmipClient. For this example, we assume that the options variable provides all the necessary information to configure the client using Basic-Auth. Additionally, we add the BatchErrorContinuationOptionPolicy with the batch error continuation option set to Continue and add an ExchangeFilterFunctions to handle HTTP errors.

KmipClientBuilder clientBuilder = new KmipClientBuilder()
        .setBaseUrl(options.getServerUrl().toString())
        .policy(new BatchErrorContinuationOptionPolicy(BatchErrorContinuationOption.CONTINUE))
        .filter(ExchangeFilterFunctions.basicAuthentication(options.getUsername(), options.getPassword()));
        .filter(ExchangeFilterFunctions.statusError(HttpStatus::isError, req -> new KmipCliException("Failed to communicate with the KMS. " + req.statusCode() )));

KmipClient client =  clientBuilder.build();

We can then use the client to send the whole batch of operation requests to the MTG KMS-Server. Once the client receives the response from the KMS-Server, it will provide us with a map containing the responses for each operation.

Map<UniqueBatchItemId, ResponseBatchItem> responseItems = kmipClient.send(requestBuilder);
For simplicity, we do not validate the response items in this example.

Finally, we can get the UUID of the newly created AES key from the response.

ResponseBatchItem aesResponse = responseMessages.get(idCreate);
ResponsePayloadCreate aesPayload = (ResponsePayloadCreate) aesResponse.getResponsePayload();

UUID keyId = UUID.fromString(((UniqueIdentifierTextString) aesPayload.getUniqueIdentifier()).getTextString());

Example Single Operation

Executing a single operation using the RequestBuilder introduces a lot of overhead. Therefore, it is easier to manually wrap the operation’s RequestPayload in a RequestBatchItem and send it directly to the server.

This example assumes we already have a KmipClient initialized.
 RequestPayloadPurge requestPayloadPurge = new RequestPayloadPurgeBuilder()
         .setUniqueIdentifier(new UniqueIdentifierTextString(uuid.toString()))
         .build();

 RequestBatchItem requestCreateBatchItem = new RequestBatchItemBuilder()
         .setOperation(Operation.PURGE)
         .setRequestPayload(
                 requestPayloadPurge
         ).build();

The KmipClient.sendSingle method sends the request to the MTG KMS-Server and reduces the overhead for retrieving the response since it only contains the response of the send request.

 ResponseBatchItem responseBatchItem = kmipClient.sendSingle(requestCreateBatchItem);
 if (responseBatchItem.getResultStatus() != ResultStatus.SUCCESS)
 {
     throw new KmipCliException(String.format("Failed to purge the managed object %s. Reason:%s - %s",
                                              uuid.toString(),
                                              responseBatchItem.getResultReason().toString(),
                                              responseBatchItem.getResultMessage()));
 }

Example Encrypt Operation

The KMIP encrypt operation KMIP SPEC, 6.1.17 can be used to instruct the server to perform encryption on the provided data using the provided managed object’s key as the encryption key. This example assumes that the data we want to encrypt is a secret message using an AES key.

This example assumes we already have a KmipClient initialized.
String toBeEncrypted = "This is a secret message!";

We start by emitting a create and activate operation to the MTG KMS-Server to create the AES key used for the encryption operation. In the create operation’s payload, we define that the KMS can only use the AES key for encryption and decryption by setting the cryptographic usage mask accordingly.

Attributes attributesForCreate = new AttributesBuilder()
                .addAttributes(CryptographicAlgorithm.AES)
                .addAttributes(new SimpleItemWrapper<>(256, Tag.CRYPTOGRAPHIC_LENGTH, ItemType.INTEGER))
                .addAttributes(CryptographicUsageMasksVirtual.of(
                                CryptographicUsageMask.DECRYPT,
                                CryptographicUsageMask.ENCRYPT
                ))
                .build();

RequestPayloadCreate requestPayloadCreate = new RequestPayloadCreateBuilder()
                .setObjectType(ObjectType.SYMMETRIC_KEY)
                .setAttributes(attributesForCreate)
                .build();

RequestPayloadActivate requestPayloadActivate = new RequestPayloadActivateBuilder()
                .build();

RequestBuilder requestBuilder = new RequestBuilder();
UUID uuidCreate = requestBuilder.addPayload(requestPayloadCreate);
UUID uuidActivate = requestBuilder.addPayload(requestPayloadActivate);

Map<UUID, ResponseBatchItem> responseMap = kmipClient.send(requestBuilder);
For simplicity, we do not validate the response items in the example.
In the example we omitted the unique identifier for the activate operation. Therefore the ID Placeholder mechanism described in KMIP-SPEC, 6.1 is used.

Next up, we can extract the UUID of the created AES key using the uuidCreate variable from above. We can then use the extracted UUID to construct and emit the encrypt request message.

ResponsePayloadCreate responsePayloadCreate = (ResponsePayloadCreate) responseMap.get(uuidCreate).getResponsePayload();
UniqueIdentifierUnion encKeyUUID = responsePayloadCreate.getUniqueIdentifier();

RequestPayloadEncrypt requestPayloadEncrypt = new RequestPayloadEncryptBuilder()
                .setData(DataUnion.of(toBeEncrypted.getBytes(StandardCharsets.UTF_8)))
                .setUniqueIdentifier(encKeyUUID)
                .setCryptographicParameters(new CryptographicParametersBuilder()
                                                            .setBlockCipherMode(BlockCipherMode.GCM)
                                                            .setTagLength(0xc)
                                                            .setRandomIV(true)
                                                            .build())
                .build();
RequestBatchItem requestBatchItem = new RequestBatchItemBuilder()
                .setOperation(Operation.ENCRYPT)
                .setRequestPayload(requestPayloadEncrypt)
                .build();

ResponseBatchItem responseBatchItem = kmipClient.sendSingle(requestBatchItem);

The response message then contains the encrypted data as a byte string.

ResponsePayloadEncrypt responsePayloadEncrypt = (ResponsePayloadEncrypt) responseBatchItem.getResponsePayload();
byte[] encryptedMessage = responsePayloadEncrypt.getData();

Example Sign Operation

The KMIP sign operation KMIP SPEC, 6.1.49 can be used to create a signature on the provided data using the MTG KMS-Server. This example demonstrates how to use the MTG KMS-Server to sign a file using an RSA key pair by emitting only a single batched request, containing the create key pair, activate and sign operation.

This example assumes we already have a KmipClient initialized.

We start by constructing the request message using the RequestBuilder class.

RequestBuilder requestBuilder = new RequestBuilder();

In the create key pair payload, we limit the cryptographic usage of the private key to sign and decrypt and the cryptographic usage of the public key to verify and encrypt by setting their cryptographic usage masks accordingly.

CommonAttributes commonAttributes = new CommonAttributesBuilder()
                .addAttributes(CryptographicAlgorithm.RSA)
                .addAttributes(new SimpleItemWrapper<>(2048, Tag.CRYPTOGRAPHIC_LENGTH, ItemType.INTEGER))
                .build();
PrivateKeyAttributes privateKeyAttributes = new PrivateKeyAttributesBuilder()
                .addAttributes(CryptographicUsageMasksVirtual.of(
                                CryptographicUsageMask.SIGN,
                                CryptographicUsageMask.DECRYPT
                ))
                .build();
PublicKeyAttributes publicKeyAttributes = new PublicKeyAttributesBuilder()
                .addAttributes(CryptographicUsageMasksVirtual.of(
                                CryptographicUsageMask.VERIFY,
                                CryptographicUsageMask.ENCRYPT
                ))
                .build();

RequestPayloadCreateKeyPair requestPayloadCreateKeyPair = new RequestPayloadCreateKeyPairBuilder()
                .setCommonAttributes(commonAttributes)
                .setPrivateKeyAttributes(privateKeyAttributes)
                .setPublicKeyAttributes(publicKeyAttributes)
                .build();

UUID createKeyPairUUID = requestBuilder.addPayload(requestPayloadCreateKeyPair);

Next up, we add two activate payloads to the RequestBuilder to ensure we can use the created private and public keys for cryptographic operations.

RequestPayloadActivate requestPayloadActivatePrivateKey = new RequestPayloadActivateBuilder()
                .setUniqueIdentifier(UniqueIdentifierUnion.of(UniqueIdentifier.CREATE_KEY_PAIR_PRIVATE_KEY))
                .build();
requestBuilder.addPayload(requestPayloadActivatePrivateKey);

RequestPayloadActivate requestPayloadActivatePublicKey = new RequestPayloadActivateBuilder()
                .setUniqueIdentifier(UniqueIdentifierUnion.of(UniqueIdentifier.CREATE_KEY_PAIR_PUBLIC_KEY))
                .build();
requestBuilder.addPayload(requestPayloadActivatePublicKey);

After ensuring the KMS can use both keys for cryptographic operations, we can add the sign operation’s payload to the RequestBuilder.

byte[] fileContent = readFileContent();

RequestPayloadSign requestPayloadSign = new RequestPayloadSignBuilder()
                .setUniqueIdentifier(UniqueIdentifierUnion.of(UniqueIdentifier.CREATE_KEY_PAIR_PRIVATE_KEY))
                .setData(DataUnion.of(fileContent))
                .setCryptographicParameters(new CryptographicParametersBuilder()
                                                            .setDigitalSignatureAlgorithm(DigitalSignatureAlgorithm.SHA_256_WITH_RSA_ENCRYPTION)
                                                            .build())
                .build();
UUID signUUID = requestBuilder.addPayload(requestPayloadSign);
We assume a readFileContent() method is already implemented and provides the content of the file.

Finally, we can emit the request to the MTG KMS-Server and extract the file signature from the response.

Map<UUID, ResponseBatchItem> responseMap = kmipClient.send(requestBuilder);

ResponsePayloadSign responsePayloadSign = (ResponsePayloadSign) responseMap.get(signUUID).getResponsePayload();
byte[] signature = responsePayloadSign.getSignatureData();

Example Source Code

The example source code we provide contains a simple Java CLI application that uses the MTG KMIP SDK. It offers four Basic Operations:

  • createKey - Creates an AES Key in the MTG KMS-Server

  • createKeypair - Creates a Keypair in the MTG KMS-Server

  • list - Lists all Managed Objects in the MTG KMS-Server

  • purge - Purges the given Managed Objects from the MTG KMS-Server

  • encrypt - Runs an encrypt operation using the provided key on the provided data.

  • sign - Calculate signature using the provided RSA key’s UUID and data.

Additional information about the CLI’s commands and options are included in the example’s directory README.md file.

Usage of KmipClientBuilder within a spring boot application

As ReactorClientHttpConnector is used for the Connection to the KMS-server, a Default-Pool-Manager is automatically used within a spring boot application which could result in memory leaks if the KmipClientBuilder is instantiated each time a call to the KMS-Server will be done and because the Default-Pool-Manager will never be freed. In order to avoid this memory leak, the connectionProvider Method of the KmipClientBuilder can be used to define an own Pool-Manager. As an Example the following code snipped can be used with the builder:

KmipClientBuilder clientBuilder = new KmipClientBuilder()
                            .connectionProvider(reactor.netty.resources.ConnectionProvider.create("http",1))
                            .......