#!/usr/bin/env python3 """Exercise the production fleet envelope with real Ed25519, never native mocks.""" import hashlib from pathlib import Path import struct import subprocess import tempfile import unittest from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey ROOT = Path(__file__).resolve().parents[1] NOW = 1791500000 CHANNEL = bytes(range(1, 17)) DOMAIN = b"MeshCoreFleet1" PUBLISHER = Ed25519PrivateKey.from_private_bytes(bytes(range(1, 33))) PUBLIC = PUBLISHER.public_key().public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw) OTHER = Ed25519PrivateKey.from_private_bytes(bytes(range(33, 65))) OTHER_PUBLIC = OTHER.public_key().public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw) FIXTURE = r''' #include #include #include #include #include #include #include #include // The production Identity interface is retained. OpenSSL implements its // signatures here so these tests cannot inherit native's always-true mock. namespace mesh { Identity::Identity() { memset(pub_key, 0, sizeof(pub_key)); } LocalIdentity::LocalIdentity(RNG* rng) { memset(prv_key, 0, sizeof(prv_key)); rng->random(prv_key, 32); EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, prv_key, 32); assert(key); size_t length = sizeof(pub_key); assert(EVP_PKEY_get_raw_public_key(key, pub_key, &length) == 1 && length == sizeof(pub_key)); EVP_PKEY_free(key); } void LocalIdentity::sign(uint8_t* signature, const uint8_t* message, int length) const { EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, prv_key, 32); EVP_MD_CTX* context = EVP_MD_CTX_new(); assert(key && context); assert(EVP_DigestSignInit(context, nullptr, nullptr, nullptr, key) == 1); size_t size = SIGNATURE_SIZE; assert(EVP_DigestSign(context, signature, &size, message, size_t(length)) == 1); assert(size == SIGNATURE_SIZE); EVP_MD_CTX_free(context); EVP_PKEY_free(key); } bool Identity::verify(const uint8_t* signature, const uint8_t* message, int length) const { EVP_PKEY* key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, pub_key, sizeof(pub_key)); EVP_MD_CTX* context = EVP_MD_CTX_new(); assert(key && context); assert(EVP_DigestVerifyInit(context, nullptr, nullptr, nullptr, key) == 1); const bool valid = EVP_DigestVerify(context, signature, SIGNATURE_SIZE, message, size_t(length)) == 1; EVP_MD_CTX_free(context); EVP_PKEY_free(key); return valid; } } // namespace mesh struct FixedRng : mesh::RNG { void random(uint8_t* output, size_t size) override { for (size_t i = 0; i < size; ++i) output[i] = uint8_t(i + 1); } }; std::vector unhex(const char* input) { const std::string text(input); assert(text.size() % 2 == 0); std::vector bytes(text.size() / 2); for (size_t i = 0; i < bytes.size(); ++i) bytes[i] = uint8_t(std::stoul(text.substr(i * 2, 2), nullptr, 16)); return bytes; } void hex(const uint8_t* bytes, size_t size) { constexpr char digits[] = "0123456789abcdef"; for (size_t i = 0; i < size; ++i) std::cout << digits[bytes[i] >> 4] << digits[bytes[i] & 15]; std::cout << '\n'; } int main(int argc, char** argv) { using Fleet = mesh::FleetCommand; if (argc == 7 && std::string(argv[1]) == "encode") { FixedRng rng; mesh::LocalIdentity publisher(&rng); const auto key = unhex(argv[2]); assert(key.size() == Fleet::KeySize); std::array target; if (!Fleet::parseTarget(argv[3], target.data())) { std::cout << "reject\n"; return 0; } std::array output; const size_t size = Fleet::encode(publisher, key.data(), uint32_t(std::stoul(argv[4])), uint32_t(std::stoul(argv[5])), target.data(), argv[6], output.data(), output.size()); if (!size) std::cout << "reject\n"; else hex(output.data(), size); return 0; } if (argc == 7 && std::string(argv[1]) == "encode2") { FixedRng rng; mesh::LocalIdentity publisher(&rng); const auto key = unhex(argv[2]); assert(key.size() == Fleet::KeySize); Fleet::Targets targets; if (!Fleet::parseTargets(argv[3], strlen(argv[3]), targets)) { std::cout << "reject\n"; return 0; } std::array output; const size_t size = Fleet::encode(publisher, key.data(), uint32_t(std::stoul(argv[4])), uint32_t(std::stoul(argv[5])), targets, argv[6], output.data(), output.size()); if (!size) std::cout << "reject\n"; else hex(output.data(), size); return 0; } if (argc == 7 && (std::string(argv[1]) == "decode" || std::string(argv[1]) == "decode2")) { const auto public_key = unhex(argv[2]), key = unhex(argv[3]), self = unhex(argv[4]), bytes = unhex(argv[6]); assert(public_key.size() == PUB_KEY_SIZE && self.size() == PUB_KEY_SIZE && key.size() == Fleet::KeySize); mesh::Identity publisher(public_key.data()); Fleet::Decoded output; if (!Fleet::decode(publisher, key.data(), bytes.data(), bytes.size(), uint32_t(std::stoul(argv[5])), self.data(), output)) { assert(output.sequence == 0 && output.expires == 0 && !output.broadcast && output.command[0] == 0); std::cout << "reject\n"; } else { std::cout << output.sequence << '\n' << output.expires << '\n' << output.command << '\n'; if (std::string(argv[1]) == "decode2") std::cout << int(output.broadcast) << '\n'; } return 0; } return 2; } ''' def envelope(command="get radio2", sequence=NOW, expires=NOW + 120, target=b"\0" * 16, channel=CHANNEL, publisher=PUBLISHER): command = command.encode("ascii") if isinstance(command, str) else command unsigned = b"FMC1" + struct.pack(" 15) def test_fmc2_rejects_short_trailing_and_signed_forbidden_commands(self): original = envelope2([(4, PUBLIC[:4]), (6, OTHER_PUBLIC[:6])]) for length in [0, 12, 13, 14, 18, 19, len(original) - 1]: self.assertEqual(self.decode(original[:length]), "reject") self.assertEqual(self.decode(original + b"\0"), "reject") for command in ["get prv.key", "set fleet.controller off", "get radio2\nreboot", b"get radio2\0"]: self.assertEqual(self.decode(envelope2([(4, PUBLIC[:4])], command)), "reject") def test_fmc2_uses_same_signed_sequence_and_clock_rules(self): entries = [(4, PUBLIC[:4])] original = envelope2(entries) self.assertEqual(struct.unpack("