about summary refs log blame commit diff
path: root/src/nix/verify.cc
blob: 8facb4bef8a241e10e547c28d0aa33bd37558bee (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                     








                         
                                    

                            
                         
                            
                      
 
               

                                                                                               
                                                                                            

                                                                                           
                                                                                                             

     









                                                     













                                                                                                  
                                                         
     

                                             
                                                 
 

                                                 
                                    

                                         

                                      



                                                
                                                            




                                                   

                                 
                                                                                          




                                                            
                                                      
                                                         


                                              
                                                      
                                                              
                                      
                                   
                                                                                                      
                                                                                             



                     
                               
 

                                      
                                                      

                                    









                                                                              
                                                                          



                                                

                                                                                                 
                                           
 
                                                            

                                                                     


                                                                                                          
                                                     
                                                 
                                                                                                   

                             


                                                          


                                
                                                              
                                    
                                                                                      



                     
                                                 

                       
                                 
                                                                                   
                                                   








                                                       
                                                                                   
                                                     







                                 
                                                 
#include "command.hh"
#include "shared.hh"
#include "store-api.hh"
#include "sync.hh"
#include "thread-pool.hh"

#include <atomic>

using namespace nix;

struct CmdVerify : StorePathsCommand
{
    bool noContents = false;
    bool noTrust = false;
    Strings substituterUris;
    size_t sigsNeeded;

    CmdVerify()
    {
        mkFlag(0, "no-contents", "do not verify the contents of each store path", &noContents);
        mkFlag(0, "no-trust", "do not verify whether each store path is trusted", &noTrust);
        mkFlag('s', "substituter", {"store-uri"}, "use signatures from specified store", 1,
            [&](Strings ss) { substituterUris.push_back(ss.front()); });
        mkIntFlag('n', "sigs-needed", "require that each path has at least N valid signatures", &sigsNeeded);
    }

    std::string name() override
    {
        return "verify";
    }

    std::string description() override
    {
        return "verify the integrity of store paths";
    }

    Examples examples() override
    {
        return {
            Example{
                "To verify the entire Nix store:",
                "nix verify --all"
            },
            Example{
                "To check whether each path in the closure of Firefox has at least 2 signatures:",
                "nix verify -r -n2 --no-contents $(type -p firefox)"
            },
        };
    }

    void run(ref<Store> store, Paths storePaths) override
    {
        std::vector<ref<Store>> substituters;
        for (auto & s : substituterUris)
            substituters.push_back(openStore(s));

        auto publicKeys = getDefaultPublicKeys();

        std::atomic<size_t> done{0};
        std::atomic<size_t> untrusted{0};
        std::atomic<size_t> corrupted{0};
        std::atomic<size_t> failed{0};

        std::string doneLabel("paths checked");
        std::string untrustedLabel("untrusted");
        std::string corruptedLabel("corrupted");
        std::string failedLabel("failed");
        //logger->setExpected(doneLabel, storePaths.size());

        ThreadPool pool;

        auto doPath = [&](const Path & storePath) {
            try {
                checkInterrupt();

                //Activity act(*logger, lvlInfo, format("checking ‘%s’") % storePath);

                auto info = store->queryPathInfo(storePath);

                if (!noContents) {

                    HashSink sink(info->narHash.type);
                    store->narFromPath(info->path, sink);

                    auto hash = sink.finish();

                    if (hash.first != info->narHash) {
                        //logger->incProgress(corruptedLabel);
                        corrupted = 1;
                        printError(
                            format("path ‘%s’ was modified! expected hash ‘%s’, got ‘%s’")
                            % info->path % printHash(info->narHash) % printHash(hash.first));
                    }

                }

                if (!noTrust) {

                    bool good = false;

                    if (info->ultimate && !sigsNeeded)
                        good = true;

                    else {

                        StringSet sigsSeen;
                        size_t actualSigsNeeded = sigsNeeded ? sigsNeeded : 1;
                        size_t validSigs = 0;

                        auto doSigs = [&](StringSet sigs) {
                            for (auto sig : sigs) {
                                if (sigsSeen.count(sig)) continue;
                                sigsSeen.insert(sig);
                                if (info->checkSignature(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) {
                                printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
                            }
                        }

                        if (validSigs >= actualSigsNeeded)
                            good = true;
                    }

                    if (!good) {
                        //logger->incProgress(untrustedLabel);
                        untrusted++;
                        printError(format("path ‘%s’ is untrusted") % info->path);
                    }

                }

                //logger->incProgress(doneLabel);
                done++;

            } catch (Error & e) {
                printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
                //logger->incProgress(failedLabel);
                failed++;
            }
        };

        for (auto & storePath : storePaths)
            pool.enqueue(std::bind(doPath, storePath));

        pool.process();

        printInfo(format("%d paths checked, %d untrusted, %d corrupted, %d failed")
            % done % untrusted % corrupted % failed);

        throw Exit(
            (corrupted ? 1 : 0) |
            (untrusted ? 2 : 0) |
            (failed ? 4 : 0));
    }
};

static RegisterCommand r1(make_ref<CmdVerify>());