Mark content-addressed paths in the Nix database and in .narinfo
This allows such paths to be imported without signatures.
This commit is contained in:
		
							parent
							
								
									36a51ecab3
								
							
						
					
					
						commit
						d961c29c9c
					
				
					 11 changed files with 146 additions and 43 deletions
				
			
		|  | @ -3213,7 +3213,7 @@ void SubstitutionGoal::tryNext() | |||
|     /* Bail out early if this substituter lacks a valid
 | ||||
|        signature. LocalStore::addToStore() also checks for this, but | ||||
|        only after we've downloaded the path. */ | ||||
|     if (worker.store.requireSigs && !info->checkSignatures(worker.store.publicKeys)) { | ||||
|     if (worker.store.requireSigs && !info->checkSignatures(worker.store, worker.store.publicKeys)) { | ||||
|         printMsg(lvlInfo, format("warning: substituter ‘%s’ does not have a valid signature for path ‘%s’") | ||||
|             % sub->getUri() % storePath); | ||||
|         tryNext(); | ||||
|  |  | |||
|  | @ -195,6 +195,13 @@ LocalStore::LocalStore(const Params & params) | |||
|             txn.commit(); | ||||
|         } | ||||
| 
 | ||||
|         if (curSchema < 10) { | ||||
|             SQLiteTxn txn(state->db); | ||||
|             if (sqlite3_exec(state->db, "alter table ValidPaths add column ca text", 0, 0, 0) != SQLITE_OK) | ||||
|                 throwSQLiteError(state->db, "upgrading database schema"); | ||||
|             txn.commit(); | ||||
|         } | ||||
| 
 | ||||
|         writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str()); | ||||
| 
 | ||||
|         lockFile(globalLock.get(), ltRead, true); | ||||
|  | @ -204,13 +211,13 @@ LocalStore::LocalStore(const Params & params) | |||
| 
 | ||||
|     /* Prepare SQL statements. */ | ||||
|     state->stmtRegisterValidPath.create(state->db, | ||||
|         "insert into ValidPaths (path, hash, registrationTime, deriver, narSize, ultimate, sigs) values (?, ?, ?, ?, ?, ?, ?);"); | ||||
|         "insert into ValidPaths (path, hash, registrationTime, deriver, narSize, ultimate, sigs, ca) values (?, ?, ?, ?, ?, ?, ?, ?);"); | ||||
|     state->stmtUpdatePathInfo.create(state->db, | ||||
|         "update ValidPaths set narSize = ?, hash = ?, ultimate = ?, sigs = ? where path = ?;"); | ||||
|         "update ValidPaths set narSize = ?, hash = ?, ultimate = ?, sigs = ?, ca = ? where path = ?;"); | ||||
|     state->stmtAddReference.create(state->db, | ||||
|         "insert or replace into Refs (referrer, reference) values (?, ?);"); | ||||
|     state->stmtQueryPathInfo.create(state->db, | ||||
|         "select id, hash, registrationTime, deriver, narSize, ultimate, sigs from ValidPaths where path = ?;"); | ||||
|         "select id, hash, registrationTime, deriver, narSize, ultimate, sigs, ca from ValidPaths where path = ?;"); | ||||
|     state->stmtQueryReferences.create(state->db, | ||||
|         "select path from Refs join ValidPaths on reference = id where referrer = ?;"); | ||||
|     state->stmtQueryReferrers.create(state->db, | ||||
|  | @ -527,6 +534,7 @@ uint64_t LocalStore::addValidPath(State & state, | |||
|         (info.narSize, info.narSize != 0) | ||||
|         (info.ultimate ? 1 : 0, info.ultimate) | ||||
|         (concatStringsSep(" ", info.sigs), !info.sigs.empty()) | ||||
|         (info.ca, !info.ca.empty()) | ||||
|         .exec(); | ||||
|     uint64_t id = sqlite3_last_insert_rowid(state.db); | ||||
| 
 | ||||
|  | @ -609,6 +617,9 @@ std::shared_ptr<ValidPathInfo> LocalStore::queryPathInfoUncached(const Path & pa | |||
|         s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 6); | ||||
|         if (s) info->sigs = tokenizeString<StringSet>(s, " "); | ||||
| 
 | ||||
|         s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 7); | ||||
|         if (s) info->ca = s; | ||||
| 
 | ||||
|         /* Get the references. */ | ||||
|         auto useQueryReferences(state->stmtQueryReferences.use()(info->id)); | ||||
| 
 | ||||
|  | @ -628,6 +639,7 @@ void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info) | |||
|         ("sha256:" + printHash(info.narHash)) | ||||
|         (info.ultimate ? 1 : 0, info.ultimate) | ||||
|         (concatStringsSep(" ", info.sigs), !info.sigs.empty()) | ||||
|         (info.ca, !info.ca.empty()) | ||||
|         (info.path) | ||||
|         .exec(); | ||||
| } | ||||
|  | @ -898,7 +910,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, const std::string & nar, | |||
|         throw Error(format("hash mismatch importing path ‘%s’; expected hash ‘%s’, got ‘%s’") % | ||||
|             info.path % info.narHash.to_string() % h.to_string()); | ||||
| 
 | ||||
|     if (requireSigs && !dontCheckSigs && !info.checkSignatures(publicKeys)) | ||||
|     if (requireSigs && !dontCheckSigs && !info.checkSignatures(*this, publicKeys)) | ||||
|         throw Error(format("cannot import path ‘%s’ because it lacks a valid signature") % info.path); | ||||
| 
 | ||||
|     addTempRoot(info.path); | ||||
|  | @ -983,6 +995,7 @@ Path LocalStore::addToStoreFromDump(const string & dump, const string & name, | |||
|             info.narHash = hash.first; | ||||
|             info.narSize = hash.second; | ||||
|             info.ultimate = true; | ||||
|             info.ca = "fixed:" + (recursive ? (std::string) "r:" : "") + h.to_string(); | ||||
|             registerValidPath(info); | ||||
|         } | ||||
| 
 | ||||
|  | @ -1014,7 +1027,8 @@ Path LocalStore::addToStore(const string & name, const Path & _srcPath, | |||
| Path LocalStore::addTextToStore(const string & name, const string & s, | ||||
|     const PathSet & references, bool repair) | ||||
| { | ||||
|     Path dstPath = computeStorePathForText(name, s, references); | ||||
|     auto hash = hashString(htSHA256, s); | ||||
|     auto dstPath = makeTextPath(name, hash, references); | ||||
| 
 | ||||
|     addTempRoot(dstPath); | ||||
| 
 | ||||
|  | @ -1034,16 +1048,17 @@ Path LocalStore::addTextToStore(const string & name, const string & s, | |||
| 
 | ||||
|             StringSink sink; | ||||
|             dumpString(s, sink); | ||||
|             auto hash = hashString(htSHA256, *sink.s); | ||||
|             auto narHash = hashString(htSHA256, *sink.s); | ||||
| 
 | ||||
|             optimisePath(realPath); | ||||
| 
 | ||||
|             ValidPathInfo info; | ||||
|             info.path = dstPath; | ||||
|             info.narHash = hash; | ||||
|             info.narHash = narHash; | ||||
|             info.narSize = sink.s->size(); | ||||
|             info.references = references; | ||||
|             info.ultimate = true; | ||||
|             info.ca = "text:" + hash.to_string(); | ||||
|             registerValidPath(info); | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,8 +17,8 @@ namespace nix { | |||
| /* Nix store and database schema version.  Version 1 (or 0) was Nix <=
 | ||||
|    0.7.  Version 2 was Nix 0.8 and 0.9.  Version 3 is Nix 0.10. | ||||
|    Version 4 is Nix 0.11.  Version 5 is Nix 0.12-0.16.  Version 6 is | ||||
|    Nix 1.0.  Version 7 is Nix 1.3. Version 9 is 1.12. */ | ||||
| const int nixSchemaVersion = 9; | ||||
|    Nix 1.0.  Version 7 is Nix 1.3. Version 10 is 1.12. */ | ||||
| const int nixSchemaVersion = 10; | ||||
| 
 | ||||
| 
 | ||||
| extern string drvsLogDir; | ||||
|  |  | |||
|  | @ -67,6 +67,10 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string & | |||
|             system = value; | ||||
|         else if (name == "Sig") | ||||
|             sigs.insert(value); | ||||
|         else if (name == "CA") { | ||||
|             if (!ca.empty()) corrupt(); | ||||
|             ca = value; | ||||
|         } | ||||
| 
 | ||||
|         pos = eol + 1; | ||||
|     } | ||||
|  | @ -101,6 +105,9 @@ std::string NarInfo::to_string() const | |||
|     for (auto sig : sigs) | ||||
|         res += "Sig: " + sig + "\n"; | ||||
| 
 | ||||
|     if (!ca.empty()) | ||||
|         res += "CA: " + ca + "\n"; | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -273,6 +273,7 @@ std::shared_ptr<ValidPathInfo> RemoteStore::queryPathInfoUncached(const Path & p | |||
|     if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 16) { | ||||
|         info->ultimate = readInt(conn->from) != 0; | ||||
|         info->sigs = readStrings<StringSet>(conn->from); | ||||
|         info->ca = readString(conn->from); | ||||
|     } | ||||
|     return info; | ||||
| } | ||||
|  |  | |||
|  | @ -6,7 +6,8 @@ create table if not exists ValidPaths ( | |||
|     deriver          text, | ||||
|     narSize          integer, | ||||
|     ultimate         integer, -- null implies "false" | ||||
|     sigs             text -- space-separated | ||||
|     sigs             text, -- space-separated | ||||
|     ca               text -- if not null, an assertion that the path is content-addressed; see ValidPathInfo | ||||
| ); | ||||
| 
 | ||||
| create table if not exists Refs ( | ||||
|  |  | |||
|  | @ -202,6 +202,22 @@ Path Store::makeFixedOutputPath(bool recursive, | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| Path Store::makeTextPath(const string & name, const Hash & hash, | ||||
|     const PathSet & references) const | ||||
| { | ||||
|     assert(hash.type == htSHA256); | ||||
|     /* Stuff the references (if any) into the type.  This is a bit
 | ||||
|        hacky, but we can't put them in `s' since that would be | ||||
|        ambiguous. */ | ||||
|     string type = "text"; | ||||
|     for (auto & i : references) { | ||||
|         type += ":"; | ||||
|         type += i; | ||||
|     } | ||||
|     return makeStorePath(type, hash, name); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| std::pair<Path, Hash> Store::computeStorePathForPath(const Path & srcPath, | ||||
|     bool recursive, HashType hashAlgo, PathFilter & filter) const | ||||
| { | ||||
|  | @ -215,16 +231,7 @@ std::pair<Path, Hash> Store::computeStorePathForPath(const Path & srcPath, | |||
| Path Store::computeStorePathForText(const string & name, const string & s, | ||||
|     const PathSet & references) const | ||||
| { | ||||
|     Hash hash = hashString(htSHA256, s); | ||||
|     /* Stuff the references (if any) into the type.  This is a bit
 | ||||
|        hacky, but we can't put them in `s' since that would be | ||||
|        ambiguous. */ | ||||
|     string type = "text"; | ||||
|     for (auto & i : references) { | ||||
|         type += ":"; | ||||
|         type += i; | ||||
|     } | ||||
|     return makeStorePath(type, hash, name); | ||||
|     return makeTextPath(name, hashString(htSHA256, s), references); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -432,9 +439,38 @@ void ValidPathInfo::sign(const SecretKey & secretKey) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| unsigned int ValidPathInfo::checkSignatures(const PublicKeys & publicKeys) const | ||||
| bool ValidPathInfo::isContentAddressed(const Store & store) const | ||||
| { | ||||
|     unsigned int good = 0; | ||||
|     auto warn = [&]() { | ||||
|         printMsg(lvlError, format("warning: path ‘%s’ claims to be content-addressed but isn't") % path); | ||||
|     }; | ||||
| 
 | ||||
|     if (hasPrefix(ca, "text:")) { | ||||
|         auto hash = parseHash(std::string(ca, 5)); | ||||
|         if (store.makeTextPath(storePathToName(path), hash, references) == path) | ||||
|             return true; | ||||
|         else | ||||
|             warn(); | ||||
|     } | ||||
| 
 | ||||
|     else if (hasPrefix(ca, "fixed:")) { | ||||
|         bool recursive = ca.compare(6, 2, "r:") == 0; | ||||
|         auto hash = parseHash(std::string(ca, recursive ? 8 : 6)); | ||||
|         if (store.makeFixedOutputPath(recursive, hash, storePathToName(path)) == path) | ||||
|             return true; | ||||
|         else | ||||
|             warn(); | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| size_t ValidPathInfo::checkSignatures(const Store & store, const PublicKeys & publicKeys) const | ||||
| { | ||||
|     if (isContentAddressed(store)) return maxSigs; | ||||
| 
 | ||||
|     size_t good = 0; | ||||
|     for (auto & sig : sigs) | ||||
|         if (checkSignature(publicKeys, sig)) | ||||
|             good++; | ||||
|  |  | |||
|  | @ -16,6 +16,13 @@ | |||
| namespace nix { | ||||
| 
 | ||||
| 
 | ||||
| struct BasicDerivation; | ||||
| struct Derivation; | ||||
| class FSAccessor; | ||||
| class NarInfoDiskCache; | ||||
| class Store; | ||||
| 
 | ||||
| 
 | ||||
| /* Size of the hash part of store paths, in base-32 characters. */ | ||||
| const size_t storePathHashLen = 32; // i.e. 160 bits
 | ||||
| 
 | ||||
|  | @ -109,6 +116,34 @@ struct ValidPathInfo | |||
| 
 | ||||
|     StringSet sigs; // note: not necessarily verified
 | ||||
| 
 | ||||
|     /* If non-empty, an assertion that the path is content-addressed,
 | ||||
|        i.e., that the store path is computed from a cryptographic hash | ||||
|        of the contents of the path, plus some other bits of data like | ||||
|        the "name" part of the path. Such a path doesn't need | ||||
|        signatures, since we don't have to trust anybody's claim that | ||||
|        the path is the output of a particular derivation. (In the | ||||
|        extensional store model, we have to trust that the *contents* | ||||
|        of an output path of a derivation were actually produced by | ||||
|        that derivation. In the intensional model, we have to trust | ||||
|        that a particular output path was produced by a derivation; the | ||||
|        path name then implies the contents.) | ||||
| 
 | ||||
|        Ideally, the content-addressability assertion would just be a | ||||
|        Boolean, and the store path would be computed from | ||||
|        ‘storePathToName(path)’, ‘narHash’ and ‘references’. However, | ||||
|        1) we've accumulated several types of content-addressed paths | ||||
|        over the years; and 2) fixed-output derivations support | ||||
|        multiple hash algorithms and serialisation methods (flat file | ||||
|        vs NAR). Thus, ‘ca’ has one of the following forms: | ||||
| 
 | ||||
|        * ‘text:sha256:<sha256 hash of file contents>’: For paths | ||||
|          computed by makeTextPath() / addTextToStore(). | ||||
| 
 | ||||
|        * ‘fixed:<r?>:<ht>:<h>’: For paths computed by | ||||
|          makeFixedOutputPath() / addToStore(). | ||||
|     */ | ||||
|     std::string ca; | ||||
| 
 | ||||
|     bool operator == (const ValidPathInfo & i) const | ||||
|     { | ||||
|         return | ||||
|  | @ -127,9 +162,15 @@ struct ValidPathInfo | |||
| 
 | ||||
|     void sign(const SecretKey & secretKey); | ||||
| 
 | ||||
|     /* Return true iff the path is verifiably content-addressed. */ | ||||
|     bool isContentAddressed(const Store & store) const; | ||||
| 
 | ||||
|     static const size_t maxSigs = std::numeric_limits<size_t>::max(); | ||||
| 
 | ||||
|     /* Return the number of signatures on this .narinfo that were
 | ||||
|        produced by one of the specified keys. */ | ||||
|     unsigned int checkSignatures(const PublicKeys & publicKeys) const; | ||||
|        produced by one of the specified keys, or maxSigs if the path | ||||
|        is content-addressed. */ | ||||
|     size_t checkSignatures(const Store & store, const PublicKeys & publicKeys) const; | ||||
| 
 | ||||
|     /* Verify a single signature. */ | ||||
|     bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const; | ||||
|  | @ -169,12 +210,6 @@ struct BuildResult | |||
| }; | ||||
| 
 | ||||
| 
 | ||||
| struct BasicDerivation; | ||||
| struct Derivation; | ||||
| class FSAccessor; | ||||
| class NarInfoDiskCache; | ||||
| 
 | ||||
| 
 | ||||
| class Store : public std::enable_shared_from_this<Store> | ||||
| { | ||||
| public: | ||||
|  | @ -234,10 +269,12 @@ public: | |||
|     Path makeFixedOutputPath(bool recursive, | ||||
|         const Hash & hash, const string & name) const; | ||||
| 
 | ||||
|     /* This is the preparatory part of addToStore() and
 | ||||
|        addToStoreFixed(); it computes the store path to which srcPath | ||||
|        is to be copied.  Returns the store path and the cryptographic | ||||
|        hash of the contents of srcPath. */ | ||||
|     Path makeTextPath(const string & name, const Hash & hash, | ||||
|         const PathSet & references) const; | ||||
| 
 | ||||
|     /* This is the preparatory part of addToStore(); it computes the
 | ||||
|        store path to which srcPath is to be copied.  Returns the store | ||||
|        path and the cryptographic hash of the contents of srcPath. */ | ||||
|     std::pair<Path, Hash> computeStorePathForPath(const Path & srcPath, | ||||
|         bool recursive = true, HashType hashAlgo = htSHA256, | ||||
|         PathFilter & filter = defaultPathFilter) const; | ||||
|  |  | |||
|  | @ -515,7 +515,8 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe | |||
|                << info->registrationTime << info->narSize; | ||||
|             if (GET_PROTOCOL_MINOR(clientVersion) >= 16) { | ||||
|                 to << info->ultimate | ||||
|                    << info->sigs; | ||||
|                    << info->sigs | ||||
|                    << info->ca; | ||||
|             } | ||||
|         } else { | ||||
|             assert(GET_PROTOCOL_MINOR(clientVersion) >= 17); | ||||
|  |  | |||
|  | @ -73,6 +73,7 @@ struct CmdPathInfo : StorePathsCommand | |||
|                 std::cout << '\t'; | ||||
|                 Strings ss; | ||||
|                 if (info->ultimate) ss.push_back("ultimate"); | ||||
|                 if (info->ca != "") ss.push_back("ca:" + info->ca); | ||||
|                 for (auto & sig : info->sigs) ss.push_back(sig); | ||||
|                 std::cout << concatStringsSep(" ", ss); | ||||
|             } | ||||
|  |  | |||
|  | @ -116,12 +116,16 @@ struct CmdVerify : StorePathsCommand | |||
|                             } | ||||
|                         }; | ||||
| 
 | ||||
|                         if (info->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs; | ||||
| 
 | ||||
|                         doSigs(info->sigs); | ||||
| 
 | ||||
|                         for (auto & store2 : substituters) { | ||||
|                             if (validSigs >= actualSigsNeeded) break; | ||||
|                             try { | ||||
|                                 doSigs(store2->queryPathInfo(info->path)->sigs); | ||||
|                                 auto info2 = store2->queryPathInfo(info->path); | ||||
|                                 if (info2->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs; | ||||
|                                 doSigs(info2->sigs); | ||||
|                             } catch (InvalidPath &) { | ||||
|                             } catch (Error & e) { | ||||
|                                 printMsg(lvlError, format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what()); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue