#include "command.hh" #include "shared.hh" #include "store-api.hh" #include "thread-pool.hh" #include "signals.hh" #include "keys.hh" #include #include "exit.hh" using namespace nix; struct CmdVerify : StorePathsCommand { bool noContents = false; bool noTrust = false; Strings substituterUris; size_t sigsNeeded = 0; CmdVerify() { addFlag({ .longName = "no-contents", .description = "Do not verify the contents of each store path.", .handler = {&noContents, true}, }); addFlag({ .longName = "no-trust", .description = "Do not verify whether each store path is trusted.", .handler = {&noTrust, true}, }); addFlag({ .longName = "substituter", .shortName = 's', .description = "Use signatures from the specified store.", .labels = {"store-uri"}, .handler = {[&](std::string s) { substituterUris.push_back(s); }} }); addFlag({ .longName = "sigs-needed", .shortName = 'n', .description = "Require that each path is signed by at least *n* different keys.", .labels = {"n"}, .handler = {&sigsNeeded} }); } std::string description() override { return "verify the integrity of store paths"; } std::string doc() override { return #include "verify.md" ; } void run(ref store, StorePaths && storePaths) override { std::vector> substituters; for (auto & s : substituterUris) substituters.push_back(openStore(s)); auto publicKeys = getDefaultPublicKeys(); Activity act(*logger, actVerifyPaths); std::atomic done{0}; std::atomic untrusted{0}; std::atomic corrupted{0}; std::atomic failed{0}; std::atomic active{0}; auto update = [&]() { act.progress(done, storePaths.size(), active, failed); }; ThreadPool pool; auto doPath = [&](const StorePath & storePath) { try { checkInterrupt(); MaintainCount> mcActive(active); update(); auto info = store->queryPathInfo(storePath); // Note: info->path can be different from storePath // for binary cache stores when using --all (since we // can't enumerate names efficiently). Activity act2(*logger, lvlInfo, actUnknown, fmt("checking '%s'", store->printStorePath(info->path))); if (!noContents) { auto hashSink = HashSink(info->narHash.algo); store->narFromPath(info->path, hashSink); auto hash = hashSink.finish(); if (hash.first != info->narHash) { corrupted++; act2.result(resCorruptedPath, store->printStorePath(info->path)); printError("path '%s' was modified! expected hash '%s', got '%s'", store->printStorePath(info->path), info->narHash.to_string(HashFormat::Nix32, true), hash.first.to_string(HashFormat::Nix32, true)); } } if (!noTrust) { bool good = false; if (info->ultimate && !sigsNeeded) good = true; else { StringSet sigsSeen; size_t actualSigsNeeded = std::max(sigsNeeded, (size_t) 1); size_t validSigs = 0; auto doSigs = [&](StringSet sigs) { for (auto sig : sigs) { if (!sigsSeen.insert(sig).second) continue; if (validSigs < ValidPathInfo::maxSigs && info->checkSignature(*store, publicKeys, sig)) validSigs++; } }; if (info->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs; doSigs(info->sigs); for (auto & store2 : substituters) { if (validSigs >= actualSigsNeeded) break; try { auto info2 = store2->queryPathInfo(info->path); if (info2->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs; doSigs(info2->sigs); } catch (InvalidPath &) { } catch (Error & e) { logError(e.info()); } } if (validSigs >= actualSigsNeeded) good = true; } if (!good) { untrusted++; act2.result(resUntrustedPath, store->printStorePath(info->path)); printError("path '%s' is untrusted", store->printStorePath(info->path)); } } done++; } catch (Error & e) { logError(e.info()); failed++; } update(); }; for (auto & storePath : storePaths) pool.enqueue(std::bind(doPath, storePath)); pool.process(); throw Exit( (corrupted ? 1 : 0) | (untrusted ? 2 : 0) | (failed ? 4 : 0)); } }; static auto rCmdVerify = registerCommand2({"store", "verify"});