#pragma once #include "signed-manifest.h" // Canonical monotonic state contract. No registry operations in this header. namespace acceptance_contract { using release_contract::Json; static bool sha(const std::string &value) { if (value.size() != 64) return false; for (char c : value) if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) return false; return true; } static bool userSid(const std::string &value) { if ((value.rfind("S-1-5-21-", 0) != 0 && value.rfind("S-1-12-1-", 0) != 0) || std::count(value.begin(), value.end(), '-') != 7) return false; PSID sid = nullptr; if (!ConvertStringSidToSidA(value.c_str(), &sid)) return false; LPSTR text = nullptr; bool valid = ConvertSidToStringSidA(sid, &text) && text && value == text; if (text) LocalFree(text); LocalFree(sid); return valid; } static std::string identity(const Json &record) { return release_contract::str(record.object.at("sid")) + "|" + release_contract::str(record.object.at("deviceId")) + "|" + release_contract::str(record.object.at("policyKeySha256")); } static void floor(const Json &value, bool deployment) { if (!release_contract::exact( value, deployment ? std::initializer_list{"minimumSequence", "acceptedDigest", "tenant"} : std::initializer_list{"minimumSequence", "acceptedDigest"}) || !sha(release_contract::str(value.object.at("acceptedDigest"))) || !release_contract::number(value.object.at("minimumSequence")) || (deployment && !release_contract::uuid( release_contract::str(value.object.at("tenant"))))) throw std::runtime_error("fixed-state-invalid"); } static Json validate(const std::string &text) { if (text.size() > 2096128) throw std::runtime_error("fixed-state-invalid"); Json state = release_contract::json(text); if (!release_contract::exact( state, {"format", "release", "deployment", "policies"}) || release_contract::str(state.object.at("format")) != "safetynet-service-floors-v1") throw std::runtime_error("fixed-state-invalid"); floor(state.object.at("release"), false); floor(state.object.at("deployment"), true); const auto &policies = state.object.at("policies"); if (policies.kind != Json::Array || policies.array.size() > 8192) throw std::runtime_error("fixed-state-invalid"); std::string previous; for (const auto &record : policies.array) { if (!release_contract::exact(record, {"sid", "deviceId", "policyKeySha256", "minimumVersion", "acceptedDigest"}) || !userSid(release_contract::str(record.object.at("sid"))) || !sha(release_contract::str(record.object.at("policyKeySha256"))) || !sha(release_contract::str(record.object.at("acceptedDigest"))) || !release_contract::number(record.object.at("minimumVersion"))) throw std::runtime_error("fixed-state-invalid"); auto device = release_contract::str(record.object.at("deviceId")); if (device.empty() || device.size() > 80 || !isalnum(static_cast(device[0]))) throw std::runtime_error("fixed-state-invalid"); for (unsigned char c : device) if (!isalnum(c) && c != '.' && c != '_' && c != '-') throw std::runtime_error("fixed-state-invalid"); auto id = identity(record); if (!previous.empty() && id <= previous) throw std::runtime_error("fixed-state-invalid"); previous = id; } return state; } static bool nondecreasing(const Json &previous, const Json &candidate, const char *field) { auto old = release_contract::number(previous.object.at(field)), next = release_contract::number(candidate.object.at(field)); return next >= old && (next != old || release_contract::str(previous.object.at("acceptedDigest")) == release_contract::str(candidate.object.at("acceptedDigest"))); } static bool transition(const Json &old, const Json &candidate) { if (!nondecreasing(old.object.at("release"), candidate.object.at("release"), "minimumSequence") || !nondecreasing(old.object.at("deployment"), candidate.object.at("deployment"), "minimumSequence") || release_contract::str(old.object.at("deployment").object.at("tenant")) != release_contract::str( candidate.object.at("deployment").object.at("tenant"))) return false; std::map records; for (const auto &record : candidate.object.at("policies").array) records.emplace(identity(record), &record); for (const auto &record : old.object.at("policies").array) { auto match = records.find(identity(record)); if (match == records.end() || !nondecreasing(record, *match->second, "minimumVersion")) return false; } return true; } } // namespace acceptance_contract