CI

kmip-ttlv - A library for (de)serializing KMIP TTLV

KMIP:

The OASIS Key Management Interoperability Protocol specifications which define message formats for the manipulation of cryptographic material on a key management server.

TTLV:

A building block of the KMIP specifications which defines how to encode and decode structured data to/from a binary form as a sequence of Tag-Type-Length-Value (aka TTLV) items.

Welcome

This crate offers a partial implementation of KMIP v1.0 TTLV (de)serialization functionality for use primarily by the Krill project. The interface offered is based on the popular Rust Serde (de)serialization framework for decorating arbitrary high level Rust "business object" structs with attributes that guide the (de)serialization process.

Scope

The primary responsibilities of this crate are handling the need to write the length before knowing how long the value will be, translating Rust types to/from TTLV types, guiding creation of correct request structures and offering useful diagnostic messages if response structures cannot be parsed.

This crate is one of potentially several crates that will be implemented to add the ability to Krill to interact with KMIP compliant servers. The current thinking is that the work consists of separate chunks for TTLV (de)serialization, KMIP business object definitions, client request/response API and the TCP+TLS client.

Note: The scope is limited to TTLV-over-TLS. Support for HTTPS instead of TLS and/or XML and/or JSON instead of TTLV binary encoding are not planned at this time as all KMIP server implementations are required to support TTLV-over-TLS.

Status

This is a work-in-progress. The interface offered by this library is expected to change and no guarantee of interface stability is made at this time. See the https://github.com/NLnetLabs/kmip/ repository for an implementation of a KMIP TTLV client that uses this crate as the core building block and also includes a sample TTLV over TLS client.

Not all TTLV types are supported:

| TTLV Type | TTLV Type Code | Supported? | |---|---|---| | Structure | 0x01 | ✔️ | | Integer | 0x02 | ✔️ | | Long Integer | 0x03 | ✔️ | | Big Integer | 0x04 | ✔️ | | Enumeration | 0x05 | ✔️ | | Boolean | 0x06 | ✔️ | | Text String | 0x07 | ✔️ | | Byte String | 0x08 | ✔️ | | Date Time | 0x09 | ✔️ | | Interval | 0x0A | |

Design goals

Example code

Based on the KMIP v1.0 specification use case defined in section 3.1.1 Create / Destroy.

The examples below assume the client code has already defined Rust structs that #[derive(Serialize)] or #[derive(Deserialize)] as appropriate to tell the Serde based (de)serializer which tag codes should be used for each data structure.

(subject to change)

Request building:

rust let req = RequestMessage( RequestHeader( ProtocolVersion( ProtocolVersionMajor(1), ProtocolVersionMinor(0)), BatchCount(1), ), vec![BatchItem( Operation::Create, RequestPayload::Create( ObjectType::SymmetricKey, TemplateAttribute(vec![ Attribute::CryptographicAlgorithm(CryptographicAlgorithm::AES), Attribute::CryptographicLength(128), Attribute::CryptographicUsageMask(0x0000_000C), // This can be made more user friendly ]), ), )], );

In the example above one cannot for example accidentally add a second Operation::Delete to the request or provide completely wrong arguments as it will fail to compile.

Response processing:

```rust let r: ResponseMessage = fromslice(ttlvwire.as_ref()).unwrap();

asserteq!(r.header.ver.major, 1); asserteq!(r.header.ver.minor, 0); asserteq!(r.header.timestamp, 0x000000004AFBE7C2); // This can be made more user friendly asserteq!(r.header.item_count, 1);

assert_eq!(r.items.len(), 1);

let item = &r.items[0]; asserteq!(item.operation, Operation::Create); asserteq!(item.status, ResultStatus::Success); asserteq!(item.payload.objecttype, ObjectType::SymmetricKey); asserteq!(&item.payload.uniqueid, "fc8833de-70d2-4ece-b063-fede3a3c59fe"); ```

Likewise rather than process and/or index into an arbitrary sequence of TTLV response key/value pairs, this strongly typed approach makes it clear which fields are available and makes them immediately usable as Rust types.

Re-use of existing metadata

The current approach makes heavy use of #[serde(rename = "0xNNNNNN")] to inform the (de)serializer of the TTLV tag that should be read from or written to the data stream for the current type, and infers the TTLV type to serialize to based on the Rust type being serialized. In certain special cases the Serde name is further (ab)used by including additional hints in the name for use by the deserializer.