Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,11 @@ if(BUILD_TESTS)
${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/node_info_json.cpp
)

add_unit_test(
identity_types_test
${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/identity_types.cpp
)

add_unit_test(tls_test ${CMAKE_CURRENT_SOURCE_DIR}/src/tls/test/main.cpp)
target_link_libraries(tls_test PRIVATE ${CMAKE_THREAD_LIBS_INIT})

Expand Down
95 changes: 95 additions & 0 deletions src/node/test/identity_types.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.

#include "service/tables/identity_types.h"

#include "ccf/kv/unit.h"

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <array>
#include <cstdint>
#include <doctest/doctest.h>
#include <limits>
#include <set>
#include <stdexcept>
#include <string>
#include <vector>

using IdentityTypeSerialiser =
ccf::kv::serialisers::BlitSerialiser<ccf::IdentityType>;

static constexpr std::array<ccf::IdentityType, 2> IDENTITY_TYPES = {
ccf::IdentityType::CLASSICAL, ccf::IdentityType::PQ};

TEST_CASE("CLASSICAL shares the serialised key of a single-Value table")
{
// ServiceValue is a Map with a single entry, whose key is 8 null bytes. A
// table keyed by IdentityType is therefore serialised identically to a
// ServiceValue for as long as CLASSICAL is its only entry.
REQUIRE(
IdentityTypeSerialiser::to_serialised(ccf::IdentityType::CLASSICAL) ==
ccf::kv::serialisers::ZeroBlitUnitCreator::get());

REQUIRE(
IdentityTypeSerialiser::to_serialised(ccf::IdentityType::PQ) !=
ccf::kv::serialisers::ZeroBlitUnitCreator::get());
}

TEST_CASE("IdentityType serialisation round-trips")
{
for (const auto identity_type : IDENTITY_TYPES)
{
const auto serialised =
IdentityTypeSerialiser::to_serialised(identity_type);
REQUIRE(serialised.size() == sizeof(uint64_t));
REQUIRE(
IdentityTypeSerialiser::from_serialised(serialised) == identity_type);
}
}

TEST_CASE("Unknown identity types are rejected")
{
const auto unknown =
ccf::kv::serialisers::BlitSerialiser<uint64_t>::to_serialised(
std::numeric_limits<uint64_t>::max());
REQUIRE_THROWS_AS(
IdentityTypeSerialiser::from_serialised(unknown), std::logic_error);
}

TEST_CASE("IdentityType names are distinct and stable")
{
// These names will appear in the ledger, so they must not change.
REQUIRE(nlohmann::json(ccf::IdentityType::CLASSICAL) == "CLASSICAL");
REQUIRE(nlohmann::json(ccf::IdentityType::PQ) == "PQ");

std::set<std::string> names;
for (const auto identity_type : IDENTITY_TYPES)
{
const nlohmann::json name = identity_type;
REQUIRE(names.insert(name.get<std::string>()).second);
}
REQUIRE(names.size() == IDENTITY_TYPES.size());
}

TEST_CASE("Identity round-trips through JSON")
{
const ccf::Identity identity{
ccf::IdentityKind::X509_SPKI_DER, std::vector<uint8_t>{1, 2, 3, 4}};

const nlohmann::json j = identity;
REQUIRE(j["kind"] == "X509_SPKI_DER");

REQUIRE(j.get<ccf::Identity>() == identity);
}

TEST_CASE("Identities round-trips through JSON")
Comment thread
maxtropets marked this conversation as resolved.
{
const ccf::Identities identities{
{ccf::IdentityType::CLASSICAL,
{ccf::IdentityKind::X509_CERT_DER, std::vector<uint8_t>{1, 2}}},
{ccf::IdentityType::PQ,
{ccf::IdentityKind::X509_SPKI_DER, std::vector<uint8_t>{3, 4}}}};

const nlohmann::json j = identities;
REQUIRE(j.get<ccf::Identities>() == identities);
}
81 changes: 81 additions & 0 deletions src/service/tables/identity_types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once

#include "ccf/ds/json.h"
#include "ccf/kv/serialisers/blit_serialiser.h"

#include <cstdint>
#include <map>
#include <stdexcept>
#include <vector>

namespace ccf
{
enum class IdentityType : uint64_t
{
CLASSICAL = 0,
PQ = 1,
};

DECLARE_JSON_ENUM(
IdentityType,
{{IdentityType::CLASSICAL, "CLASSICAL"}, {IdentityType::PQ, "PQ"}});

enum class IdentityKind : uint8_t
{
X509_CERT_DER = 0,
X509_SPKI_DER = 1,
};

DECLARE_JSON_ENUM(
IdentityKind,
{{IdentityKind::X509_CERT_DER, "X509_CERT_DER"},
{IdentityKind::X509_SPKI_DER, "X509_SPKI_DER"}});

using IdentityValue = std::vector<uint8_t>;

struct Identity
{
IdentityKind kind;
IdentityValue value;

bool operator==(const Identity&) const = default;
};

DECLARE_JSON_TYPE(Identity);
DECLARE_JSON_REQUIRED_FIELDS(Identity, kind, value);
Comment thread
maxtropets marked this conversation as resolved.

using Identities = std::map<IdentityType, Identity>;
}

namespace ccf::kv::serialisers
{
// IdentityType is used as a KV key by tables which were previously a single
// Value. CLASSICAL is 0, so it serialises to the same bytes as the unit key
// of those tables, keeping their serialised form unchanged.
template <>
struct BlitSerialiser<ccf::IdentityType>
{
static SerialisedEntry to_serialised(const ccf::IdentityType& identity_type)
{
return BlitSerialiser<uint64_t>::to_serialised(
static_cast<uint64_t>(identity_type));
}

static ccf::IdentityType from_serialised(const SerialisedEntry& data)
{
const auto value = BlitSerialiser<uint64_t>::from_serialised(data);
switch (value)
{
case static_cast<uint64_t>(ccf::IdentityType::CLASSICAL):
return ccf::IdentityType::CLASSICAL;
case static_cast<uint64_t>(ccf::IdentityType::PQ):
return ccf::IdentityType::PQ;
default:
throw std::logic_error(
fmt::format("Unknown identity type: {}", value));
}
}
};
}