blob: 8f378dfc28f9615e64a22d42862a6124eab439e1 [file] [edit]
#include <doctest/doctest.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <sframe/sframe.h>
#include <iostream>
#include <map> // for map
#include <stdexcept> // for invalid_argument
#include <string> // for basic_string, operator==
using namespace sframe;
static void
ensure_fips_if_required()
{
const auto* require = std::getenv("REQUIRE_FIPS");
if (require && FIPS_mode() == 0) {
REQUIRE(FIPS_mode_set(1) == 1);
}
}
static bytes
from_hex(const std::string& hex)
{
if (hex.length() % 2 == 1) {
throw std::invalid_argument("Odd-length hex string");
}
auto len = int(hex.length() / 2);
auto out = bytes(len);
for (int i = 0; i < len; i += 1) {
auto byte = hex.substr(2 * i, 2);
out[i] = static_cast<uint8_t>(strtol(byte.c_str(), nullptr, 16));
}
return out;
}
template<typename T>
bytes
to_bytes(const T& range)
{
return bytes(range.begin(), range.end());
}
TEST_CASE("SFrame Known-Answer")
{
ensure_fips_if_required();
struct KnownAnswerTest
{
bytes key;
bytes short_kid_ctr0;
bytes short_kid_ctr1;
bytes short_kid_ctr2;
bytes long_kid_short_ctr;
bytes long_kid_long_ctr;
};
const auto short_kid = KeyID(0x07);
const auto long_kid = KeyID(0xffff);
const auto long_ctr = KeyID(0x0100);
const auto plaintext = from_hex("00010203");
const std::map<CipherSuite, KnownAnswerTest> cases{
{ CipherSuite::AES_CM_128_HMAC_SHA256_4,
{
from_hex("101112131415161718191a1b1c1d1e1f"),
from_hex("070023b51101cc7ebc3d"),
from_hex("0701aa0743f6aa3a2b9b"),
from_hex("0702eae82433853983b7"),
from_hex("0affff0023b51101efb2441d"),
from_hex("1affff01001981bb4f7281d098"),
} },
{ CipherSuite::AES_CM_128_HMAC_SHA256_8,
{
from_hex("202122232425262728292a2b2c2d2e2f"),
from_hex("070022067e92bbacd94627c087b8"),
from_hex("0701d868b21f4ba897d19490eaa5"),
from_hex("070266de5b93b640ba637ae569dc"),
from_hex("0affff0022067e92dac1fe9af8fd6a07"),
from_hex("1affff01005ba58d136415a9799dc921f9"),
} },
{ CipherSuite::AES_GCM_128_SHA256,
{
from_hex("303132333435363738393a3b3c3d3e3f"),
from_hex("070048310f3bb26f3ee3ceed7756efe2f32078766c56"),
from_hex("070145c8c2cd60103b4a5f3477635e1b1e82f62fd280"),
from_hex("07021ea6e7b06ca32c143772066478856563dd255634"),
from_hex("0affff0048310f3b6ac967bc3e472395932eff498d3eebab"),
from_hex("1affff0100f1f838df579e32e95341dc97ae8bbd21b77c8494"),
} },
{ CipherSuite::AES_GCM_256_SHA512,
{
from_hex(
"404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"),
from_hex("0700b591faaff9b9965fcabfd0949a2bb67be4179753"),
from_hex("0701d555e6652a3f2ee36ea8c6723e57c4544025d0e7"),
from_hex("070222e5fcd46f28a2a9fa784f3b2d1aa03d481b7acc"),
from_hex("0affff00b591faafd7f6bfe6ab4dc1de52c78338395796ab"),
from_hex("1affff01007b0e9ee925743869e071d413def89374beab3bb4"),
} },
};
auto pt_out = bytes(plaintext.size());
auto ct_out = bytes(plaintext.size() + max_overhead);
for (auto& pair : cases) {
auto& suite = pair.first;
auto& tc = pair.second;
auto ctx = Context(suite);
ctx.add_key(short_kid, tc.key);
ctx.add_key(long_kid, tc.key);
// KID=0x07, CTR=0, 1, 2
auto ct0 = to_bytes(ctx.protect(short_kid, ct_out, plaintext));
auto ct1 = to_bytes(ctx.protect(short_kid, ct_out, plaintext));
auto ct2 = to_bytes(ctx.protect(short_kid, ct_out, plaintext));
CHECK(ct0 == tc.short_kid_ctr0);
CHECK(ct1 == tc.short_kid_ctr1);
CHECK(ct2 == tc.short_kid_ctr2);
CHECK(plaintext == to_bytes(ctx.unprotect(pt_out, ct0)));
CHECK(plaintext == to_bytes(ctx.unprotect(pt_out, ct1)));
CHECK(plaintext == to_bytes(ctx.unprotect(pt_out, ct2)));
// KID=0xffff, CTR=0
auto ctLS = to_bytes(ctx.protect(long_kid, ct_out, plaintext));
for (Counter ctr = 1; ctr < long_ctr; ctr++) {
ctx.protect(long_kid, ct_out, plaintext);
}
auto ctLL = to_bytes(ctx.protect(long_kid, ct_out, plaintext));
CHECK(to_bytes(ctLS) == tc.long_kid_short_ctr);
CHECK(to_bytes(ctLL) == tc.long_kid_long_ctr);
CHECK(plaintext == to_bytes(ctx.unprotect(pt_out, ct0)));
CHECK(plaintext == to_bytes(ctx.unprotect(pt_out, ct1)));
CHECK(plaintext == to_bytes(ctx.unprotect(pt_out, ct2)));
}
}
TEST_CASE("SFrame Round-Trip")
{
ensure_fips_if_required();
const auto rounds = 1 << 9;
const auto kid = KeyID(0x42);
const auto plaintext = from_hex("00010203");
const std::map<CipherSuite, bytes> keys{
{ CipherSuite::AES_CM_128_HMAC_SHA256_4,
from_hex("101112131415161718191a1b1c1d1e1f") },
{ CipherSuite::AES_CM_128_HMAC_SHA256_8,
from_hex("202122232425262728292a2b2c2d2e2f") },
{ CipherSuite::AES_GCM_128_SHA256,
from_hex("303132333435363738393a3b3c3d3e3f") },
{ CipherSuite::AES_GCM_256_SHA512,
from_hex("404142434445464748494a4b4c4d4e4f"
"505152535455565758595a5b5c5d5e5f") },
};
auto pt_out = bytes(plaintext.size());
auto ct_out = bytes(plaintext.size() + max_overhead);
for (auto& pair : keys) {
auto& suite = pair.first;
auto& key = pair.second;
auto send = Context(suite);
send.add_key(kid, key);
auto recv = Context(suite);
recv.add_key(kid, key);
for (int i = 0; i < rounds; i++) {
auto encrypted = to_bytes(send.protect(kid, ct_out, plaintext));
auto decrypted = to_bytes(recv.unprotect(pt_out, encrypted));
CHECK(decrypted == plaintext);
}
}
}
TEST_CASE("MLS Known-Answer")
{
ensure_fips_if_required();
struct KnownAnswerTest
{
using Epoch = std::vector<bytes>;
std::vector<Epoch> epochs;
};
const auto plaintext = from_hex("00010203");
const auto epoch_bits = 4;
const auto epoch_ids = std::vector<MLSContext::EpochID>{
0x00,
0x0f,
0x10,
};
const auto epoch_secrets = std::vector<bytes>{
from_hex("00000000000000000000000000000000"),
from_hex("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"),
from_hex("10101010101010101010101010101010"),
};
const auto sender_ids = std::vector<MLSContext::SenderID>{
0x0a,
0xaa,
0xaaa,
};
const std::map<CipherSuite, KnownAnswerTest> cases{
{ CipherSuite::AES_CM_128_HMAC_SHA256_4,
{ {
{
from_hex("09a000a099f9cfcebe0016"),
from_hex("0a0aa000102ea6af868bda78"),
from_hex("0aaaa0009c0aa3c3dc43d075"),
},
{
from_hex("09af008414bb5861dec7d0"),
from_hex("0a0aaf004486695c578d1d7b"),
from_hex("0aaaaf00da9a202a28d52f29"),
},
{
from_hex("09a0008f7b4591e6f1bc5b"),
from_hex("0a0aa00039743979f1e9e9f5"),
from_hex("0aaaa000658794f1db8a4553"),
},
} } },
{ CipherSuite::AES_CM_128_HMAC_SHA256_8,
{ {
{
from_hex("09a000a099f9cfcebe0016ec6d4089"),
from_hex("0a0aa000102ea6af868bda7839a896e4"),
from_hex("0aaaa0009c0aa3c3dc43d07567ed7c50"),
},
{
from_hex("09af008414bb5861dec7d0e1e2bc71"),
from_hex("0a0aaf004486695c578d1d7b90e9b557"),
from_hex("0aaaaf00da9a202a28d52f29f4bf0ddf"),
},
{
from_hex("09a0008f7b4591e6f1bc5bd3568ba2"),
from_hex("0a0aa00039743979f1e9e9f57e4a5553"),
from_hex("0aaaa000658794f1db8a45534bfde555"),
},
} } },
{ CipherSuite::AES_GCM_128_SHA256,
{ {
{
from_hex("09a000f32e41cdb15b8c9c3bd57e4ed008f056fa263df3"),
from_hex("0a0aa000ad96fdf39faf8711d53279f8549ad4fb23f1f8aa"),
from_hex("0aaaa000c79771b0d97bf9035b920fb1bb565c4025cf8a47"),
},
{
from_hex("09af00feb0c2b242d3c8a9aec464fc92f55c9539d5caa8"),
from_hex("0a0aaf00c0afce4867ee782c45de14a1990ea5576f41fa52"),
from_hex("0aaaaf00d034912de869721e8ea2e5724d3eb69f4b7c7e6a"),
},
{
from_hex("09a00003fa0e4e4c36bc0aed031c56b1db488c525831b3"),
from_hex("0a0aa000184a009fdfa7d0ee2c36a9e9ee1d21663b4dcde1"),
from_hex("0aaaa0008f2e842d16d4ec69b23623b7bd9838e4f906bab1"),
},
} } },
{ CipherSuite::AES_GCM_256_SHA512,
{ {
{
from_hex("09a0007878e804d643a86c6ec1711ee2b6a9e6aa4d9be8"),
from_hex("0a0aa00083f5083c175e74c484d837e35d6e359ef5dfc66a"),
from_hex("0aaaa0000144a05a1c3c75691b5597d01d1517d5ebc92460"),
},
{
from_hex("09af002a2be564b2a788abd838f01cfcec315563bdf708"),
from_hex("0a0aaf00ca411f242081522129078b6c5239f5e8baf10d67"),
from_hex("0aaaaf0011a1a4ea5a7796589931acc62c3a6ccf5008e3cc"),
},
{
from_hex("09a0003f5d9b66df64c7cb3cce99952028990a3869d3a8"),
from_hex("0a0aa0000b8a680c5bfc4efb8fb68041b5f63441e9aaaa85"),
from_hex("0aaaa00061c7c6a5b2882f037aea330533e0381d1f25e074"),
},
} } },
};
auto pt_out = bytes(plaintext.size());
auto ct_out = bytes(plaintext.size() + max_overhead);
for (const auto& pair : cases) {
auto& suite = pair.first;
auto& tc = pair.second;
auto ctx = MLSContext(suite, epoch_bits);
CHECK(tc.epochs.size() == epoch_ids.size());
for (size_t i = 0; i < tc.epochs.size(); i++) {
ctx.add_epoch(epoch_ids[i], epoch_secrets[i]);
CHECK(tc.epochs[i].size() == sender_ids.size());
for (size_t j = 0; j < tc.epochs[i].size(); j++) {
auto encrypted =
ctx.protect(epoch_ids[i], sender_ids[j], ct_out, plaintext);
CHECK(tc.epochs[i][j] == to_bytes(encrypted));
auto decrypted = ctx.unprotect(pt_out, tc.epochs[i][j]);
CHECK(plaintext == to_bytes(decrypted));
}
}
}
}
TEST_CASE("MLS Round-Trip")
{
ensure_fips_if_required();
const auto epoch_bits = 2;
const auto test_epochs = 1 << (epoch_bits + 1);
const auto epoch_rounds = 10;
const auto plaintext = from_hex("00010203");
const auto sender_id_a = MLSContext::SenderID(0xA0A0A0A0);
const auto sender_id_b = MLSContext::SenderID(0xA1A1A1A1);
const std::vector<CipherSuite> suites{
CipherSuite::AES_CM_128_HMAC_SHA256_4,
CipherSuite::AES_CM_128_HMAC_SHA256_8,
CipherSuite::AES_GCM_128_SHA256,
CipherSuite::AES_GCM_256_SHA512,
};
auto pt_out = bytes(plaintext.size());
auto ct_out = bytes(plaintext.size() + max_overhead);
for (auto& suite : suites) {
auto member_a = MLSContext(suite, epoch_bits);
auto member_b = MLSContext(suite, epoch_bits);
for (MLSContext::EpochID epoch_id = 0; epoch_id < test_epochs; epoch_id++) {
const auto sframe_epoch_secret = bytes(8, uint8_t(epoch_id));
member_a.add_epoch(epoch_id, sframe_epoch_secret);
member_b.add_epoch(epoch_id, sframe_epoch_secret);
for (int i = 0; i < epoch_rounds; i++) {
auto encrypted_ab =
member_a.protect(epoch_id, sender_id_a, ct_out, plaintext);
auto decrypted_ab = member_b.unprotect(pt_out, encrypted_ab);
CHECK(plaintext == to_bytes(decrypted_ab));
auto encrypted_ba =
member_b.protect(epoch_id, sender_id_b, ct_out, plaintext);
auto decrypted_ba = member_a.unprotect(pt_out, encrypted_ba);
CHECK(plaintext == to_bytes(decrypted_ba));
}
}
}
}
TEST_CASE("MLS Known-Answer with Context")
{
ensure_fips_if_required();
struct KnownAnswerTest
{
using ContextCases = std::vector<bytes>;
using SenderCases = std::vector<ContextCases>;
std::vector<SenderCases> epochs;
};
const auto plaintext = from_hex("00010203");
const auto epoch_bits = 4;
// const auto sender_bits = 12;
const auto epoch_ids = std::vector<MLSContext::EpochID>{
0x00,
0x0f,
0x10,
};
const auto epoch_secrets = std::vector<bytes>{
from_hex("00000000000000000000000000000000"),
from_hex("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"),
from_hex("10101010101010101010101010101010"),
};
const auto sender_ids = std::vector<MLSContext::SenderID>{
0x0a,
0xaa,
0xaaa,
};
const auto context_ids = std::vector<MLSContext::ContextID>{
0x0b,
0xbb,
0xbbb,
};
const auto sender_bits = 12;
const std::map<CipherSuite, KnownAnswerTest> cases{
{ CipherSuite::AES_CM_128_HMAC_SHA256_4,
{ {
{
{
from_hex("0b0b00a000e78b66f2396ad3fb"),
from_hex("0bbb00a000627f7a610c7d5114"),
from_hex("0c0bbb00a000010247612644e928"),
},
{
from_hex("0b0b0aa000065aacf40d149781"),
from_hex("0bbb0aa0003db4ff6316a3b4aa"),
from_hex("0c0bbb0aa000804e43f1e3b30c01"),
},
{
from_hex("0b0baaa0008ee32c082a6baced"),
from_hex("0bbbaaa000d158bf21f2be0aff"),
from_hex("0c0bbbaaa000ddb84e167700afcb"),
},
},
{
{
from_hex("0b0b00af00645a02cbd4dd5469"),
from_hex("0bbb00af00436630a155d66c9e"),
from_hex("0c0bbb00af000160419ebc8f5181"),
},
{
from_hex("0b0b0aaf009c1c9c9a6103a004"),
from_hex("0bbb0aaf003f0045b076151853"),
from_hex("0c0bbb0aaf00e638bf4dbafe35dc"),
},
{
from_hex("0b0baaaf002c21652122601e05"),
from_hex("0bbbaaaf001a40ca810397ca64"),
from_hex("0c0bbbaaaf00cbdcb9e4e401ee56"),
},
},
{
{
from_hex("0b0b00a000dadd1533c0418d7a"),
from_hex("0bbb00a000144ac3cfd5e4a563"),
from_hex("0c0bbb00a000b753901426810fcd"),
},
{
from_hex("0b0b0aa00006be6cd4471e3028"),
from_hex("0bbb0aa0008cea2fb844ed6440"),
from_hex("0c0bbb0aa000ad1376f533ebf44a"),
},
{
from_hex("0b0baaa000a20b1f3ed8499f8a"),
from_hex("0bbbaaa0006f311c5a7aaf0289"),
from_hex("0c0bbbaaa000ede89b44263ee250"),
},
},
} } },
{ CipherSuite::AES_CM_128_HMAC_SHA256_8,
{ {
{
{
from_hex("0b0b00a000e78b66f2396ad3fbb4a56007"),
from_hex("0bbb00a000627f7a610c7d51149811b320"),
from_hex("0c0bbb00a000010247612644e9287540ce73"),
},
{
from_hex("0b0b0aa000065aacf40d14978172ebf918"),
from_hex("0bbb0aa0003db4ff6316a3b4aadd53d398"),
from_hex("0c0bbb0aa000804e43f1e3b30c0197cfd76b"),
},
{
from_hex("0b0baaa0008ee32c082a6baced795e34b5"),
from_hex("0bbbaaa000d158bf21f2be0affa7f4a66d"),
from_hex("0c0bbbaaa000ddb84e167700afcbc8d2fe11"),
},
},
{
{
from_hex("0b0b00af00645a02cbd4dd546959a200ef"),
from_hex("0bbb00af00436630a155d66c9e16890d09"),
from_hex("0c0bbb00af000160419ebc8f51814fb3271a"),
},
{
from_hex("0b0b0aaf009c1c9c9a6103a004cab06cd0"),
from_hex("0bbb0aaf003f0045b07615185317303eb9"),
from_hex("0c0bbb0aaf00e638bf4dbafe35dc1853cc73"),
},
{
from_hex("0b0baaaf002c21652122601e056a27bc1d"),
from_hex("0bbbaaaf001a40ca810397ca646e2b08c3"),
from_hex("0c0bbbaaaf00cbdcb9e4e401ee566e91e386"),
},
},
{
{
from_hex("0b0b00a000dadd1533c0418d7ab42e193b"),
from_hex("0bbb00a000144ac3cfd5e4a563b8766bad"),
from_hex("0c0bbb00a000b753901426810fcdd91261a2"),
},
{
from_hex("0b0b0aa00006be6cd4471e30285c6e51df"),
from_hex("0bbb0aa0008cea2fb844ed64406dc495b6"),
from_hex("0c0bbb0aa000ad1376f533ebf44a3f778c81"),
},
{
from_hex("0b0baaa000a20b1f3ed8499f8a740a02a5"),
from_hex("0bbbaaa0006f311c5a7aaf02895ea699ed"),
from_hex("0c0bbbaaa000ede89b44263ee25013fc615d"),
},
},
} } },
{ CipherSuite::AES_GCM_128_SHA256,
{ {
{
{
from_hex("0b0b00a0006ecb201768cf6a0f14bbee09ad490c5a4e215650"),
from_hex("0bbb00a000bc4944c23dd62883911c247c4d42fb9cd1a60883"),
from_hex("0c0bbb00a000ea232bd73f103aebef947a487de72cbf4fae7add"),
},
{
from_hex("0b0b0aa000d0ead9e0b2bb2e52f82c1e377c27a49115694cc5"),
from_hex("0bbb0aa000fc894af5b173474384dc9b08d65875a85eeb42fa"),
from_hex("0c0bbb0aa0005df598693c6f1e5d567869302ad52064aba28157"),
},
{
from_hex("0b0baaa000051a97e25e94ab650b41890a0faa3747164ee2c0"),
from_hex("0bbbaaa000be93b0c301782fc4b7abf0a66b36120138fc86b9"),
from_hex("0c0bbbaaa0000e10273c8829af3eed30ad753763662e436a565d"),
},
},
{
{
from_hex("0b0b00af0034f26ddfee46ec7844f3d99e2895f1ba4325c74d"),
from_hex("0bbb00af00df339bf635ca2ae17d2f07b05e8edffd04518ae7"),
from_hex("0c0bbb00af000f3f4f26bb98f7a57870e376ddddf0da8ad9c6ae"),
},
{
from_hex("0b0b0aaf00d89a7e4ec825a3842f28c3ea40c8049f0cfe2084"),
from_hex("0bbb0aaf002278d74c851303f0e57553dc3e1933c3459a8487"),
from_hex("0c0bbb0aaf0076bccc828a1178dd36400b59a72d330e79e2bcc6"),
},
{
from_hex("0b0baaaf001f07a6d0f61dc787b82ca0a13b38093910df4eff"),
from_hex("0bbbaaaf00b69d6ada3cf060f7b010bb9b00fba19326e4d749"),
from_hex("0c0bbbaaaf00fd3231ba3a9a5db1e87a01d5ce4cd74f742ae46c"),
},
},
{
{
from_hex("0b0b00a000a054bb91c3097de21b58fea1fc1ccbc88570ebe5"),
from_hex("0bbb00a000e1d4a1511cf16bb6c222672a91d26ab33505c356"),
from_hex("0c0bbb00a0001865b9043654d287c8f3de32cc7cc6ad1bdfcfa9"),
},
{
from_hex("0b0b0aa0005fad155349c32ddd6dca93ac6a60c0c9b9533eb8"),
from_hex("0bbb0aa0003753e39c068bbfebe74ea6e2bb5234d90a5e7d6c"),
from_hex("0c0bbb0aa000ae9ca700d753cafc556525a5348bb4ad4ece56b9"),
},
{
from_hex("0b0baaa000e025c92b32b421efe6d6e1b9a04949498acaa9be"),
from_hex("0bbbaaa000d079005f5e967a5408b29de179a69db552f41843"),
from_hex("0c0bbbaaa000258742303cfbbcbf26ab10b490e866729242779b"),
},
},
} } },
{ CipherSuite::AES_GCM_256_SHA512,
{ {
{
{
from_hex("0b0b00a0008aa688a274acba1d92314a0f98794e1e50191392"),
from_hex("0bbb00a0005437ed5f1545ce989de0eb38f02f1ed06c74bcbe"),
from_hex("0c0bbb00a000439084e68b408b2430e9077739d9d0d53129188e"),
},
{
from_hex("0b0b0aa000af72f544b51c937d217ab488ed44db7c18b5fe16"),
from_hex("0bbb0aa00046c7750b1951594d8a12d76e4366b248d4422793"),
from_hex("0c0bbb0aa0002c5825a674df250f82b7b51dd01583689664db7c"),
},
{
from_hex("0b0baaa0008036596f12dcc552d0f03a794430d629439a205d"),
from_hex("0bbbaaa0009237f7cb947e9aedb97b0bf69557604c2c3356f5"),
from_hex("0c0bbbaaa000de9955378ee32a3aef148cd7ce05b93ee2508a74"),
},
},
{
{
from_hex("0b0b00af001c3d824df4c26e2d76d65f40840491fb577e7d26"),
from_hex("0bbb00af001e14b7f4ddc1b8fe5c29d279c7f35f46652b6265"),
from_hex("0c0bbb00af00d638340784a28863256a470667cb8521dd682f2d"),
},
{
from_hex("0b0b0aaf00f5ace84a9ac311216588637d012519d42461a698"),
from_hex("0bbb0aaf006f2d1fc9a688382e5b85b01d9f49563a0aa80d29"),
from_hex("0c0bbb0aaf00d8a865e3655d0a322106d35c3375ac3837a852c0"),
},
{
from_hex("0b0baaaf00a9b54ca17d87a0f4a64260fdb374ff60331a06c4"),
from_hex("0bbbaaaf00450e533cc76f31ffcd7080c2ec3d3e6ace9e9638"),
from_hex("0c0bbbaaaf0048a8028b1a28719158ab19c6fd1a0bf2e532f26b"),
},
},
{
{
from_hex("0b0b00a00015df4f7a226bd82e403c95eb0473c3b499b91c88"),
from_hex("0bbb00a00052e7cd8bb8cb68fde112740b154fd7ac63dfe45d"),
from_hex("0c0bbb00a00065539ae6be900e527bed4df63f9d5a28f0308659"),
},
{
from_hex("0b0b0aa0003617ffb2846dff0d01f73cb768fe332fb25187da"),
from_hex("0bbb0aa0006f6f9695211ec704a8374eea34768a29d28015b1"),
from_hex("0c0bbb0aa000dd52ace0a6cec57446268a52e8b0ae9f88c541d6"),
},
{
from_hex("0b0baaa000e37e4c8e7805ed3ec6d26f828825855c5f7b1be5"),
from_hex("0bbbaaa00028b3993f0ff97d32547dc88b1479ab6f5e7626c6"),
from_hex("0c0bbbaaa0001e93ae8c8daa541697b206ccff31ff12050ac035"),
},
},
} } },
};
auto pt_out = bytes(plaintext.size());
auto ct_out = bytes(plaintext.size() + max_overhead);
for (const auto& pair : cases) {
auto& suite = pair.first;
auto& tc = pair.second;
auto ctx = MLSContext(suite, epoch_bits);
CHECK(tc.epochs.size() == epoch_ids.size());
for (size_t i = 0; i < tc.epochs.size(); i++) {
ctx.add_epoch(epoch_ids[i], epoch_secrets[i], sender_bits);
CHECK(tc.epochs[i].size() == sender_ids.size());
for (size_t j = 0; j < tc.epochs[i].size(); j++) {
CHECK(tc.epochs[i][j].size() == context_ids.size());
for (size_t k = 0; k < tc.epochs[i][j].size(); k++) {
auto encrypted = ctx.protect(
epoch_ids[i], sender_ids[j], context_ids[k], ct_out, plaintext);
CHECK(tc.epochs[i][j][k] == to_bytes(encrypted));
auto decrypted = ctx.unprotect(pt_out, tc.epochs[i][j][k]);
CHECK(plaintext == to_bytes(decrypted));
}
}
}
}
}
TEST_CASE("MLS Round-Trip with context")
{
ensure_fips_if_required();
const auto epoch_bits = 4;
const auto test_epochs = 1 << (epoch_bits + 1);
const auto epoch_rounds = 10;
const auto plaintext = from_hex("00010203");
const auto sender_id_a = MLSContext::SenderID(0xA0A0A0A0);
const auto sender_id_b = MLSContext::SenderID(0xA1A1A1A1);
const auto sender_id_bits = size_t(32);
const auto context_id_0 = 0xB0B0;
const auto context_id_1 = 0xB1B1;
const std::vector<CipherSuite> suites{
CipherSuite::AES_CM_128_HMAC_SHA256_4,
CipherSuite::AES_CM_128_HMAC_SHA256_8,
CipherSuite::AES_GCM_128_SHA256,
CipherSuite::AES_GCM_256_SHA512,
};
auto pt_out = bytes(plaintext.size());
auto ct_out_1 = bytes(plaintext.size() + max_overhead);
auto ct_out_0 = bytes(plaintext.size() + max_overhead);
for (auto& suite : suites) {
auto member_a_0 = MLSContext(suite, epoch_bits);
auto member_a_1 = MLSContext(suite, epoch_bits);
auto member_b = MLSContext(suite, epoch_bits);
for (MLSContext::EpochID epoch_id = 0; epoch_id < test_epochs; epoch_id++) {
const auto sframe_epoch_secret = bytes(8, uint8_t(epoch_id));
member_a_0.add_epoch(epoch_id, sframe_epoch_secret, sender_id_bits);
member_a_1.add_epoch(epoch_id, sframe_epoch_secret, sender_id_bits);
member_b.add_epoch(epoch_id, sframe_epoch_secret);
for (int i = 0; i < epoch_rounds; i++) {
auto encrypted_ab_0 = member_a_0.protect(
epoch_id, sender_id_a, context_id_0, ct_out_0, plaintext);
auto decrypted_ab_0 =
to_bytes(member_b.unprotect(pt_out, encrypted_ab_0));
CHECK(plaintext == decrypted_ab_0);
auto encrypted_ab_1 = member_a_1.protect(
epoch_id, sender_id_a, context_id_1, ct_out_1, plaintext);
auto decrypted_ab_1 =
to_bytes(member_b.unprotect(pt_out, encrypted_ab_1));
CHECK(plaintext == decrypted_ab_1);
CHECK(to_bytes(encrypted_ab_0) != to_bytes(encrypted_ab_1));
auto encrypted_ba =
member_b.protect(epoch_id, sender_id_b, ct_out_0, plaintext);
auto decrypted_ba_0 =
to_bytes(member_a_0.unprotect(pt_out, encrypted_ba));
auto decrypted_ba_1 =
to_bytes(member_a_1.unprotect(pt_out, encrypted_ba));
CHECK(plaintext == decrypted_ba_0);
CHECK(plaintext == decrypted_ba_1);
}
}
}
}
TEST_CASE("MLS Failure after Purge")
{
ensure_fips_if_required();
const auto suite = CipherSuite::AES_GCM_128_SHA256;
const auto epoch_bits = 2;
const auto plaintext = from_hex("00010203");
const auto sender_id_a = MLSContext::SenderID(0xA0A0A0A0);
const auto sframe_epoch_secret_1 = bytes(32, 1);
const auto sframe_epoch_secret_2 = bytes(32, 2);
auto pt_out = bytes(plaintext.size());
auto ct_out = bytes(plaintext.size() + max_overhead);
auto member_a = MLSContext(suite, epoch_bits);
auto member_b = MLSContext(suite, epoch_bits);
// Install epoch 1 and create a cipihertext
const auto epoch_id_1 = MLSContext::EpochID(1);
member_a.add_epoch(epoch_id_1, sframe_epoch_secret_1);
member_b.add_epoch(epoch_id_1, sframe_epoch_secret_1);
const auto enc_ab_1 =
member_a.protect(epoch_id_1, sender_id_a, ct_out, plaintext);
const auto enc_ab_1_data = to_bytes(enc_ab_1);
// Install epoch 2
const auto epoch_id_2 = MLSContext::EpochID(2);
member_a.add_epoch(epoch_id_2, sframe_epoch_secret_2);
member_b.add_epoch(epoch_id_2, sframe_epoch_secret_2);
// Purge epoch 1 and verify failure
member_a.purge_before(epoch_id_2);
member_b.purge_before(epoch_id_2);
CHECK_THROWS_AS(member_a.protect(epoch_id_1, sender_id_a, ct_out, plaintext),
invalid_parameter_error);
CHECK_THROWS_AS(member_b.unprotect(pt_out, enc_ab_1_data),
invalid_parameter_error);
const auto enc_ab_2 =
member_a.protect(epoch_id_2, sender_id_a, ct_out, plaintext);
const auto dec_ab_2 = member_b.unprotect(pt_out, enc_ab_2);
CHECK(plaintext == to_bytes(dec_ab_2));
}