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:
Eelco Dolstra 2016-08-03 13:17:11 +02:00
parent 36a51ecab3
commit d961c29c9c
11 changed files with 146 additions and 43 deletions

View file

@ -3213,7 +3213,7 @@ void SubstitutionGoal::tryNext()
/* Bail out early if this substituter lacks a valid /* Bail out early if this substituter lacks a valid
signature. LocalStore::addToStore() also checks for this, but signature. LocalStore::addToStore() also checks for this, but
only after we've downloaded the path. */ 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") printMsg(lvlInfo, format("warning: substituter %s does not have a valid signature for path %s")
% sub->getUri() % storePath); % sub->getUri() % storePath);
tryNext(); tryNext();

View file

@ -195,6 +195,13 @@ LocalStore::LocalStore(const Params & params)
txn.commit(); 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()); writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str());
lockFile(globalLock.get(), ltRead, true); lockFile(globalLock.get(), ltRead, true);
@ -204,13 +211,13 @@ LocalStore::LocalStore(const Params & params)
/* Prepare SQL statements. */ /* Prepare SQL statements. */
state->stmtRegisterValidPath.create(state->db, 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, 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, state->stmtAddReference.create(state->db,
"insert or replace into Refs (referrer, reference) values (?, ?);"); "insert or replace into Refs (referrer, reference) values (?, ?);");
state->stmtQueryPathInfo.create(state->db, 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, state->stmtQueryReferences.create(state->db,
"select path from Refs join ValidPaths on reference = id where referrer = ?;"); "select path from Refs join ValidPaths on reference = id where referrer = ?;");
state->stmtQueryReferrers.create(state->db, state->stmtQueryReferrers.create(state->db,
@ -527,6 +534,7 @@ uint64_t LocalStore::addValidPath(State & state,
(info.narSize, info.narSize != 0) (info.narSize, info.narSize != 0)
(info.ultimate ? 1 : 0, info.ultimate) (info.ultimate ? 1 : 0, info.ultimate)
(concatStringsSep(" ", info.sigs), !info.sigs.empty()) (concatStringsSep(" ", info.sigs), !info.sigs.empty())
(info.ca, !info.ca.empty())
.exec(); .exec();
uint64_t id = sqlite3_last_insert_rowid(state.db); 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); s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 6);
if (s) info->sigs = tokenizeString<StringSet>(s, " "); if (s) info->sigs = tokenizeString<StringSet>(s, " ");
s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 7);
if (s) info->ca = s;
/* Get the references. */ /* Get the references. */
auto useQueryReferences(state->stmtQueryReferences.use()(info->id)); auto useQueryReferences(state->stmtQueryReferences.use()(info->id));
@ -628,6 +639,7 @@ void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
("sha256:" + printHash(info.narHash)) ("sha256:" + printHash(info.narHash))
(info.ultimate ? 1 : 0, info.ultimate) (info.ultimate ? 1 : 0, info.ultimate)
(concatStringsSep(" ", info.sigs), !info.sigs.empty()) (concatStringsSep(" ", info.sigs), !info.sigs.empty())
(info.ca, !info.ca.empty())
(info.path) (info.path)
.exec(); .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") % throw Error(format("hash mismatch importing path %s; expected hash %s, got %s") %
info.path % info.narHash.to_string() % h.to_string()); 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); throw Error(format("cannot import path %s because it lacks a valid signature") % info.path);
addTempRoot(info.path); addTempRoot(info.path);
@ -983,6 +995,7 @@ Path LocalStore::addToStoreFromDump(const string & dump, const string & name,
info.narHash = hash.first; info.narHash = hash.first;
info.narSize = hash.second; info.narSize = hash.second;
info.ultimate = true; info.ultimate = true;
info.ca = "fixed:" + (recursive ? (std::string) "r:" : "") + h.to_string();
registerValidPath(info); registerValidPath(info);
} }
@ -1014,7 +1027,8 @@ Path LocalStore::addToStore(const string & name, const Path & _srcPath,
Path LocalStore::addTextToStore(const string & name, const string & s, Path LocalStore::addTextToStore(const string & name, const string & s,
const PathSet & references, bool repair) const PathSet & references, bool repair)
{ {
Path dstPath = computeStorePathForText(name, s, references); auto hash = hashString(htSHA256, s);
auto dstPath = makeTextPath(name, hash, references);
addTempRoot(dstPath); addTempRoot(dstPath);
@ -1034,16 +1048,17 @@ Path LocalStore::addTextToStore(const string & name, const string & s,
StringSink sink; StringSink sink;
dumpString(s, sink); dumpString(s, sink);
auto hash = hashString(htSHA256, *sink.s); auto narHash = hashString(htSHA256, *sink.s);
optimisePath(realPath); optimisePath(realPath);
ValidPathInfo info; ValidPathInfo info;
info.path = dstPath; info.path = dstPath;
info.narHash = hash; info.narHash = narHash;
info.narSize = sink.s->size(); info.narSize = sink.s->size();
info.references = references; info.references = references;
info.ultimate = true; info.ultimate = true;
info.ca = "text:" + hash.to_string();
registerValidPath(info); registerValidPath(info);
} }

View file

@ -17,8 +17,8 @@ namespace nix {
/* Nix store and database schema version. Version 1 (or 0) was 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. 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 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. */ Nix 1.0. Version 7 is Nix 1.3. Version 10 is 1.12. */
const int nixSchemaVersion = 9; const int nixSchemaVersion = 10;
extern string drvsLogDir; extern string drvsLogDir;

View file

@ -67,6 +67,10 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
system = value; system = value;
else if (name == "Sig") else if (name == "Sig")
sigs.insert(value); sigs.insert(value);
else if (name == "CA") {
if (!ca.empty()) corrupt();
ca = value;
}
pos = eol + 1; pos = eol + 1;
} }
@ -101,6 +105,9 @@ std::string NarInfo::to_string() const
for (auto sig : sigs) for (auto sig : sigs)
res += "Sig: " + sig + "\n"; res += "Sig: " + sig + "\n";
if (!ca.empty())
res += "CA: " + ca + "\n";
return res; return res;
} }

View file

@ -273,6 +273,7 @@ std::shared_ptr<ValidPathInfo> RemoteStore::queryPathInfoUncached(const Path & p
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 16) { if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 16) {
info->ultimate = readInt(conn->from) != 0; info->ultimate = readInt(conn->from) != 0;
info->sigs = readStrings<StringSet>(conn->from); info->sigs = readStrings<StringSet>(conn->from);
info->ca = readString(conn->from);
} }
return info; return info;
} }

View file

@ -6,7 +6,8 @@ create table if not exists ValidPaths (
deriver text, deriver text,
narSize integer, narSize integer,
ultimate integer, -- null implies "false" 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 ( create table if not exists Refs (

View file

@ -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, std::pair<Path, Hash> Store::computeStorePathForPath(const Path & srcPath,
bool recursive, HashType hashAlgo, PathFilter & filter) const 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, Path Store::computeStorePathForText(const string & name, const string & s,
const PathSet & references) const const PathSet & references) const
{ {
Hash hash = hashString(htSHA256, s); return makeTextPath(name, hashString(htSHA256, s), references);
/* 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);
} }
@ -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) for (auto & sig : sigs)
if (checkSignature(publicKeys, sig)) if (checkSignature(publicKeys, sig))
good++; good++;

View file

@ -16,6 +16,13 @@
namespace nix { namespace nix {
struct BasicDerivation;
struct Derivation;
class FSAccessor;
class NarInfoDiskCache;
class Store;
/* Size of the hash part of store paths, in base-32 characters. */ /* Size of the hash part of store paths, in base-32 characters. */
const size_t storePathHashLen = 32; // i.e. 160 bits const size_t storePathHashLen = 32; // i.e. 160 bits
@ -109,6 +116,34 @@ struct ValidPathInfo
StringSet sigs; // note: not necessarily verified 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 bool operator == (const ValidPathInfo & i) const
{ {
return return
@ -117,19 +152,25 @@ struct ValidPathInfo
&& references == i.references; && references == i.references;
} }
/* Return a fingerprint of the store path to be used in binary /* Return a fingerprint of the store path to be used in binary
cache signatures. It contains the store path, the base-32 cache signatures. It contains the store path, the base-32
SHA-256 hash of the NAR serialisation of the path, the size of SHA-256 hash of the NAR serialisation of the path, the size of
the NAR, and the sorted references. The size field is strictly the NAR, and the sorted references. The size field is strictly
speaking superfluous, but might prevent endless/excessive data speaking superfluous, but might prevent endless/excessive data
attacks. */ attacks. */
std::string fingerprint() const; std::string fingerprint() const;
void sign(const SecretKey & secretKey); 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 /* Return the number of signatures on this .narinfo that were
produced by one of the specified keys. */ produced by one of the specified keys, or maxSigs if the path
unsigned int checkSignatures(const PublicKeys & publicKeys) const; is content-addressed. */
size_t checkSignatures(const Store & store, const PublicKeys & publicKeys) const;
/* Verify a single signature. */ /* Verify a single signature. */
bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const; 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> class Store : public std::enable_shared_from_this<Store>
{ {
public: public:
@ -234,10 +269,12 @@ public:
Path makeFixedOutputPath(bool recursive, Path makeFixedOutputPath(bool recursive,
const Hash & hash, const string & name) const; const Hash & hash, const string & name) const;
/* This is the preparatory part of addToStore() and Path makeTextPath(const string & name, const Hash & hash,
addToStoreFixed(); it computes the store path to which srcPath const PathSet & references) const;
is to be copied. Returns the store path and the cryptographic
hash of the contents of srcPath. */ /* 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, std::pair<Path, Hash> computeStorePathForPath(const Path & srcPath,
bool recursive = true, HashType hashAlgo = htSHA256, bool recursive = true, HashType hashAlgo = htSHA256,
PathFilter & filter = defaultPathFilter) const; PathFilter & filter = defaultPathFilter) const;

View file

@ -515,7 +515,8 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe
<< info->registrationTime << info->narSize; << info->registrationTime << info->narSize;
if (GET_PROTOCOL_MINOR(clientVersion) >= 16) { if (GET_PROTOCOL_MINOR(clientVersion) >= 16) {
to << info->ultimate to << info->ultimate
<< info->sigs; << info->sigs
<< info->ca;
} }
} else { } else {
assert(GET_PROTOCOL_MINOR(clientVersion) >= 17); assert(GET_PROTOCOL_MINOR(clientVersion) >= 17);

View file

@ -73,6 +73,7 @@ struct CmdPathInfo : StorePathsCommand
std::cout << '\t'; std::cout << '\t';
Strings ss; Strings ss;
if (info->ultimate) ss.push_back("ultimate"); 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); for (auto & sig : info->sigs) ss.push_back(sig);
std::cout << concatStringsSep(" ", ss); std::cout << concatStringsSep(" ", ss);
} }

View file

@ -116,12 +116,16 @@ struct CmdVerify : StorePathsCommand
} }
}; };
if (info->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs;
doSigs(info->sigs); doSigs(info->sigs);
for (auto & store2 : substituters) { for (auto & store2 : substituters) {
if (validSigs >= actualSigsNeeded) break; if (validSigs >= actualSigsNeeded) break;
try { 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 (InvalidPath &) {
} catch (Error & e) { } catch (Error & e) {
printMsg(lvlError, format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what()); printMsg(lvlError, format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());