6 releases

new 0.3.1 Sep 21, 2021
0.3.0 Sep 15, 2021
0.2.0 Sep 13, 2021
0.1.2 Aug 30, 2021

#108 in Encoding

49 downloads per month
Used in kmip-protocol

BSD-3-Clause

255KB
3.5K SLoC

CI Crate Docs

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 by the kmip-protocol crate. If you are looking to add KMIP support to your product you should use the kmip-protocol crate instead. TTLV is defined within, but is independent of, KMIP, so in theory it could also be used to (de)serialize data for applications other than KMIP.

Purpose

This crate provides low-level (de)serialization of Rust primitives (e.g. i32) from/to the equivalent KMIP TTLV byte representation. It offers both a Serde Derive based single to_/from_ call style API for (de)serialization of entire Rust type hierarchies (which is most easily driven using Serde Derive attributes) and a lower-level API for (de)serializing one TTLV field (tag, type, length or value) at a time for complete control.

The scope is limited at present to the binary TTLV protocol. Support for XML or JSON representation as defined in later KMIP specifications is not in scope.

Documentation

Full API documentation can be seen at https://docs.rs/kmip-ttlv/.

Status

This crate is offered on as-is basis with no stability, quality or correctness guarantees. Use it at your own risk.

See https://github.com/NLnetLabs/kmip-ttlv/blob/main/src/tests/ for various automated tests of the low-level and high level (Serde based) APIs. Limited manual testing has been performed successfully against PyKMIP and Kryptus Cloud HSM servers.

Issue reports, feature requests, and contributions can be submitted to our GitHub repository.

The capabilities of this crate and the TTLV and Rust data types supported are those that were needed to provide a foundation for the kmip-protocol crate. As such this crate does not yet support every possible TTLV or Rust type.

Not all TTLV types are supported:

TTLV Type TTLV Type Code Supported?
Structure 0x01
Integer 0x02
Long Integer 0x03
Big Integer 0x04 (serialization is only supported with the low-level API, not with Serde)
Enumeration 0x05
Boolean 0x06
Text String 0x07
Byte String 0x08
Date Time 0x09
Interval 0x0A

Design goals

  • Offer a strongly typed interface that prevents incorrect composition of low-level building blocks in ways that have no correct meaning in the a higher level KMIP interface specification. Leverage the Rust compile time capabilities to prevent writing of incorrect requests where possible, so that incorrect usage of the protocol at runtime is minimized.
  • Support composition of high level KMIP request structures succinctly and in such a way that the written code is clearly relatable to the KMIP specifications.
  • Using deserialized data shouldnot require knowledge of the KMIP specifications in detail, i.e. it should be in terms of Rust types, not TTLV types and the objects interacted with should have clearly named fields and only have response fields relevant to the request that was submitted.
  • TTLV tag codes should be defined near to the type definition that they tag.

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:

// serialize the request
let req = RequestMessage(
    RequestHeader(
        request::ProtocolVersion(ProtocolVersionMajor(1), ProtocolVersionMinor(0)),
        Option::<MaximumResponseSize>::None,
        Option::<Authentication>::None,
        BatchCount(1),
    ),
    vec![BatchItem(
        Operation::Create,
        Option::<UniqueBatchItemID>::None,
        RequestPayload::Create(
            ObjectType::SymmetricKey,
            TemplateAttribute::named(
                "Template1".into(),
                vec![
                    Attribute::CryptographicAlgorithm(CryptographicAlgorithm::AES),
                    Attribute::CryptographicLength(128),
                    Attribute::CryptographicUsageMask(
                        CryptographicUsageMask::Encrypt | CryptographicUsageMask::Decrypt,
                    ),
                ],
            ),
        ),
    )],
);

let ttlv_wire: Vec<u8> = to_vec(&req).unwrap();
// now write the `ttlv_wire` request bytes to an open TLS connection to the server

Response processing:

// read the `ttlv_wire` response bytes from an open TLS connection to the server:
let ttlv_wire: Vec<u8> = ...;

// deserialize the response
let res: ResponseMessage = from_slice(ttlv_wire.as_ref()).unwrap();

assert_eq!(res.header.protocol_version.major, 1);
assert_eq!(res.header.protocol_version.minor, 0);
assert_eq!(res.header.timestamp, 0x4AFBE7C5);
assert_eq!(res.header.batch_count, 1);
assert_eq!(res.batch_items.len(), 1);

let item = &res.batch_items[0];
assert!(matches!(item.result_status, ResultStatus::Success));
assert!(matches!(item.operation, Some(Operation::Create)));
assert!(matches!(&item.payload, Some(ResponsePayload::Create(_))));

if let Some(ResponsePayload::Create(payload)) = item.payload.as_ref() {
    assert!(matches!(payload.object_type, ObjectType::SymmetricKey));
    assert_eq!(&payload.unique_identifier, KEY_ID);
} 

Working with timestamps:

Timestamps are stored in TTLV Date-Time format which is converted to a 64-bit integer which has no real world meaning until you interpret it correctly and in the context of the correct timezone. Working with the 64-bit integer values directly is probably not practical. This crate does NOT currently provide a way to work more easily with these values but doing so is fairly easy with crates already available in the Rust ecosystem.

For example, for the example above which is based on an official KMIP use case, the use case description states that the 0x4AFBE7C5 value is equivalent to Thu Nov 12 11:47:32 CET 2009. Below we use the chrono crate to demonstrate that this is true and to show how you can work with Date-Time values in your application.

let one_hour_in_seconds = 3600;
let cet_tz = chrono::offset::FixedOffset::east(one_hour_in_seconds);
let cet_ts = cet_tz.timestamp(res.header.timestamp);
assert_eq!(cet_ts, cet_tz.ymd(2009,11,12).and_hms(11,47,32)
assert_eq!(cet_ts.format("%a %b %e %T %Z %Y").to_string(), "Thu Nov 12 11:47:32 +01:00 2009");

Dependencies

~0.4–3MB
~57K SLoC