mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-13 20:25:53 +08:00
[fix] persist device signing keys outside the cache directory
This commit is contained in:
1 parent
24f91c608a
commit
b3be25ad80
4 files changed
+58
-26
No files matched your search
@@ -70,28 +70,38 @@ struct PrivateText {
|
||||
void Validate(const nlohmann::json& document) {
|
||||
Require(document.is_object() && document.at("version") == 1);
|
||||
}
|
||||
|
||||
std::optional<nlohmann::json> ReadLocked(const std::filesystem::path& directory) {
|
||||
const auto path = directory / "identity.json";
|
||||
if (std::filesystem::symlink_status(path).type() ==
|
||||
std::filesystem::file_type::not_found) return std::nullopt;
|
||||
PrivateText text{private_file::Read(path)};
|
||||
auto saved = nlohmann::json::parse(text.value, nullptr, false);
|
||||
Require(!saved.is_discarded());
|
||||
Validate(saved);
|
||||
return saved;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Update(const std::filesystem::path& cache,
|
||||
const std::function<void(nlohmann::json&)>& update) {
|
||||
private_file::EnsureDirectory(cache, true);
|
||||
StoreLock lock(cache / "identity.json.lock");
|
||||
const auto path = cache / "identity.json";
|
||||
const bool exists = std::filesystem::symlink_status(path).type() !=
|
||||
std::filesystem::file_type::not_found;
|
||||
nlohmann::json saved = {{"version", 1}};
|
||||
if (exists) {
|
||||
PrivateText text{private_file::Read(path)};
|
||||
saved = nlohmann::json::parse(text.value, nullptr, false);
|
||||
Require(!saved.is_discarded());
|
||||
}
|
||||
Validate(saved);
|
||||
std::optional<nlohmann::json> Read(const std::filesystem::path& directory) {
|
||||
private_file::EnsureDirectory(directory, true);
|
||||
StoreLock lock(directory / "identity.json.lock");
|
||||
return ReadLocked(directory);
|
||||
}
|
||||
|
||||
void Update(const std::filesystem::path& directory,
|
||||
const std::function<void(nlohmann::json&, bool)>& update) {
|
||||
private_file::EnsureDirectory(directory, true);
|
||||
StoreLock lock(directory / "identity.json.lock");
|
||||
auto previous = ReadLocked(directory);
|
||||
auto saved = previous ? std::move(*previous) : nlohmann::json{{"version", 1}};
|
||||
PrivateText before{saved.dump()};
|
||||
update(saved);
|
||||
update(saved, previous.has_value());
|
||||
Validate(saved);
|
||||
PrivateText after{saved.dump()};
|
||||
Require(after.value.size() <= 64 * 1024);
|
||||
if (!exists || before.value != after.value) private_file::Write(path, after.value);
|
||||
if (!previous || before.value != after.value)
|
||||
private_file::Write(directory / "identity.json", after.value);
|
||||
}
|
||||
|
||||
} // namespace crossdesk::device_identity_store
|
||||
@@ -10,14 +10,17 @@
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace crossdesk::device_identity_store {
|
||||
|
||||
// The shared device signing key lives in cache/identity.json. Read the latest
|
||||
// document under a process lock, apply an update, then atomically persist it.
|
||||
// Device signing keys live in the persistent trusted-device directory.
|
||||
// Read the latest document under a process lock, then atomically persist it.
|
||||
// Throw on corruption, unsafe permissions, lock contention or persistence error.
|
||||
void Update(const std::filesystem::path& cache,
|
||||
const std::function<void(nlohmann::json&)>& update);
|
||||
// Read returns nullopt only when the identity file is absent (legacy migration).
|
||||
std::optional<nlohmann::json> Read(const std::filesystem::path& directory);
|
||||
void Update(const std::filesystem::path& directory,
|
||||
const std::function<void(nlohmann::json&, bool exists)>& update);
|
||||
|
||||
} // namespace crossdesk::device_identity_store
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
constexpr size_t kMaxTrustedDevices = 128;
|
||||
void Require(bool ok) {
|
||||
if (!ok) throw std::runtime_error("Invalid or unavailable trusted device identity");
|
||||
void Require(bool ok, const char* error = "Invalid or unavailable trusted device identity") {
|
||||
if (!ok) throw std::runtime_error(error);
|
||||
}
|
||||
std::string Hex(const uint8_t* data, size_t size) {
|
||||
constexpr char digits[] = "0123456789abcdef";
|
||||
@@ -81,7 +81,19 @@ TrustedDevices::TrustedDevices(const std::filesystem::path& directory,
|
||||
}
|
||||
}
|
||||
|
||||
device_identity_store::Update(cache, [&](nlohmann::json& identity) {
|
||||
device_identity_store::Update(directory, [&](nlohmann::json& identity, bool identity_exists) {
|
||||
bool legacy_identity_exists = false;
|
||||
if (!identity_exists) {
|
||||
// The persistent copy is authoritative once migration has completed.
|
||||
// Leave the old cache copy intact so an interrupted migration is retryable.
|
||||
if (auto previous = device_identity_store::Read(cache)) {
|
||||
identity = std::move(*previous);
|
||||
legacy_identity_exists = true;
|
||||
}
|
||||
} else {
|
||||
Require(identity.contains("trust_private_key"),
|
||||
"Persistent device signing key is missing; restore the original identity");
|
||||
}
|
||||
if (!identity.contains("trust_private_key") && !legacy_key.value.empty())
|
||||
identity["trust_private_key"] = legacy_key.value;
|
||||
if (identity.contains("trust_private_key")) {
|
||||
@@ -94,6 +106,8 @@ TrustedDevices::TrustedDevices(const std::filesystem::path& directory,
|
||||
secret.data(), secret.size()));
|
||||
OPENSSL_cleanse(secret.data(), secret.size());
|
||||
} else {
|
||||
Require(!exists && !legacy_identity_exists,
|
||||
"Device signing key is missing from an existing installation; restore the original identity");
|
||||
key_.reset(EVP_PKEY_Q_keygen(nullptr, nullptr, "ED25519"));
|
||||
Require(bool(key_));
|
||||
std::array<uint8_t, 32> secret{};
|
||||
|
||||
@@ -270,7 +270,9 @@ void SharedHostIdentity::Import() {
|
||||
private_file::EnsureDirectory(cache_, true);
|
||||
private_file::EnsureDirectory(trusted_);
|
||||
Json identity{{"version", 1}};
|
||||
if (std::filesystem::exists(cache_ / "identity.json"))
|
||||
if (std::filesystem::exists(trusted_ / "identity.json"))
|
||||
identity = PrivateDocument(trusted_ / "identity.json");
|
||||
else if (std::filesystem::exists(cache_ / "identity.json"))
|
||||
identity = PrivateDocument(cache_ / "identity.json");
|
||||
Require(identity.is_object() && identity.value("version", 0) == 1,
|
||||
"Invalid existing device identity store");
|
||||
@@ -284,7 +286,7 @@ void SharedHostIdentity::Import() {
|
||||
cache.checksum = credential_cache::Checksum(&cache, offsetof(credential_cache::CacheV3, checksum));
|
||||
// Validate everything before replacing any user identity data. Both stores
|
||||
// stay private to this process's user; their DACLs are never relaxed.
|
||||
private_file::Write(cache_ / "identity.json", identity.dump());
|
||||
private_file::Write(trusted_ / "identity.json", identity.dump());
|
||||
private_file::Write(trusted_ / "devices.json", data.at("trusted_devices").dump());
|
||||
private_file::Write(cache_ / "secure_cache_v3.enc",
|
||||
std::string(reinterpret_cast<const char*>(&cache), sizeof(cache)));
|
||||
@@ -303,7 +305,10 @@ bool SharedHostIdentity::Publish(const std::string& host_config, std::string& er
|
||||
std::memset(cache.base.iv, 0, sizeof(cache.base.iv));
|
||||
cache.checksum = credential_cache::Checksum(&cache, offsetof(credential_cache::CacheV3, checksum));
|
||||
const std::string identity_cache(reinterpret_cast<const char*>(&cache), sizeof(cache));
|
||||
const auto identity = PrivateDocument(cache_ / "identity.json");
|
||||
// CLI setup can publish an installation that has not run GUI migration yet.
|
||||
const auto identity = PrivateDocument(
|
||||
std::filesystem::exists(trusted_ / "identity.json")
|
||||
? trusted_ / "identity.json" : cache_ / "identity.json");
|
||||
const auto key = identity.at("trust_private_key").get<std::string>();
|
||||
Require(Unhex(key).size() == 32, "Invalid device identity key");
|
||||
const auto devices = PrivateDocument(trusted_ / "devices.json");
|
||||
|
||||
Reference in new issue
Block a user