Add C++ functions for .narinfo processing / signing
This is currently only used by the Hydra queue runner rework, but like
eff5021eaa it presumably will be useful
for the C++ rewrite of nix-push and
download-from-binary-cache. (@shlevy)
			
			
This commit is contained in:
		
							parent
							
								
									5ac27053e9
								
							
						
					
					
						commit
						c4d22997f3
					
				
					 7 changed files with 304 additions and 17 deletions
				
			
		|  | @ -10,6 +10,7 @@ | |||
| #include "globals.hh" | ||||
| #include "store-api.hh" | ||||
| #include "util.hh" | ||||
| #include "crypto.hh" | ||||
| 
 | ||||
| #if HAVE_SODIUM | ||||
| #include <sodium.h> | ||||
|  | @ -235,19 +236,12 @@ SV * convertHash(char * algo, char * s, int toBase32) | |||
|         } | ||||
| 
 | ||||
| 
 | ||||
| SV * signString(SV * secretKey_, char * msg) | ||||
| SV * signString(char * secretKey_, char * msg) | ||||
|     PPCODE: | ||||
|         try { | ||||
| #if HAVE_SODIUM | ||||
|             STRLEN secretKeyLen; | ||||
|             unsigned char * secretKey = (unsigned char *) SvPV(secretKey_, secretKeyLen); | ||||
|             if (secretKeyLen != crypto_sign_SECRETKEYBYTES) | ||||
|                 throw Error("secret key is not valid"); | ||||
| 
 | ||||
|             unsigned char sig[crypto_sign_BYTES]; | ||||
|             unsigned long long sigLen; | ||||
|             crypto_sign_detached(sig, &sigLen, (unsigned char *) msg, strlen(msg), secretKey); | ||||
|             XPUSHs(sv_2mortal(newSVpv((char *) sig, sigLen))); | ||||
|             auto sig = SecretKey(secretKey_).signDetached(msg); | ||||
|             XPUSHs(sv_2mortal(newSVpv(sig.c_str(), sig.size()))); | ||||
| #else | ||||
|             throw Error("Nix was not compiled with libsodium, required for signed binary cache support"); | ||||
| #endif | ||||
|  |  | |||
|  | @ -258,13 +258,10 @@ for (my $n = 0; $n < scalar @storePaths2; $n++) { | |||
|     } | ||||
| 
 | ||||
|     if (defined $secretKeyFile) { | ||||
|         my $s = readFile $secretKeyFile; | ||||
|         chomp $s; | ||||
|         my ($keyName, $secretKey) = split ":", $s; | ||||
|         die "invalid secret key file ‘$secretKeyFile’\n" unless defined $keyName && defined $secretKey; | ||||
|         my $secretKey = readFile $secretKeyFile; | ||||
|         my $fingerprint = fingerprintPath($storePath, $narHash, $narSize, $refs); | ||||
|         my $sig = encode_base64(signString(decode_base64($secretKey), $fingerprint), ""); | ||||
|         $info .= "Sig: $keyName:$sig\n"; | ||||
|         my $sig = signString($secretKey, $fingerprint); | ||||
|         $info .= "Sig: $sig\n"; | ||||
|     } | ||||
| 
 | ||||
|     my $pathHash = substr(basename($storePath), 0, 32); | ||||
|  |  | |||
							
								
								
									
										79
									
								
								src/libstore/crypto.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/libstore/crypto.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | |||
| #include "crypto.hh" | ||||
| #include "util.hh" | ||||
| 
 | ||||
| #if HAVE_SODIUM | ||||
| #include <sodium.h> | ||||
| #endif | ||||
| 
 | ||||
| namespace nix { | ||||
| 
 | ||||
| static std::pair<std::string, std::string> split(const string & s) | ||||
| { | ||||
|     size_t colon = s.find(':'); | ||||
|     if (colon == std::string::npos || colon == 0) | ||||
|         return {"", ""}; | ||||
|     return {std::string(s, 0, colon), std::string(s, colon + 1)}; | ||||
| } | ||||
| 
 | ||||
| Key::Key(const string & s) | ||||
| { | ||||
|     auto ss = split(s); | ||||
| 
 | ||||
|     name = ss.first; | ||||
|     key = ss.second; | ||||
| 
 | ||||
|     if (name == "" || key == "") | ||||
|         throw Error("secret key is corrupt"); | ||||
| 
 | ||||
|     key = base64Decode(key); | ||||
| } | ||||
| 
 | ||||
| SecretKey::SecretKey(const string & s) | ||||
|     : Key(s) | ||||
| { | ||||
| #if HAVE_SODIUM | ||||
|     if (key.size() != crypto_sign_SECRETKEYBYTES) | ||||
|         throw Error("secret key is not valid"); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| std::string SecretKey::signDetached(const std::string & data) const | ||||
| { | ||||
| #if HAVE_SODIUM | ||||
|     unsigned char sig[crypto_sign_BYTES]; | ||||
|     unsigned long long sigLen; | ||||
|     crypto_sign_detached(sig, &sigLen, (unsigned char *) data.data(), data.size(), | ||||
|         (unsigned char *) key.data()); | ||||
|     return name + ":" + base64Encode(std::string((char *) sig, sigLen)); | ||||
| #else | ||||
|     throw Error("Nix was not compiled with libsodium, required for signed binary cache support"); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| PublicKey::PublicKey(const string & s) | ||||
|     : Key(s) | ||||
| { | ||||
| #if HAVE_SODIUM | ||||
|     if (key.size() != crypto_sign_PUBLICKEYBYTES) | ||||
|         throw Error("public key is not valid"); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| bool verifyDetached(const std::string & data, const std::string & sig, | ||||
|     const PublicKeys & publicKeys) | ||||
| { | ||||
|     auto ss = split(sig); | ||||
| 
 | ||||
|     auto key = publicKeys.find(ss.first); | ||||
|     if (key == publicKeys.end()) return false; | ||||
| 
 | ||||
|     auto sig2 = base64Decode(ss.second); | ||||
|     if (sig2.size() != crypto_sign_BYTES) | ||||
|         throw Error("signature is not valid"); | ||||
| 
 | ||||
|     return crypto_sign_verify_detached((unsigned char *) sig2.data(), | ||||
|         (unsigned char *) data.data(), data.size(), | ||||
|         (unsigned char *) key->second.key.data()) == 0; | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										40
									
								
								src/libstore/crypto.hh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/libstore/crypto.hh
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "types.hh" | ||||
| 
 | ||||
| #include <map> | ||||
| 
 | ||||
| namespace nix { | ||||
| 
 | ||||
| struct Key | ||||
| { | ||||
|     std::string name; | ||||
|     std::string key; | ||||
| 
 | ||||
|     /* Construct Key from a string in the format
 | ||||
|        ‘<name>:<key-in-base64>’. */ | ||||
|     Key(const std::string & s); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| struct SecretKey : Key | ||||
| { | ||||
|     SecretKey(const std::string & s); | ||||
| 
 | ||||
|     /* Return a detached signature of the given string. */ | ||||
|     std::string signDetached(const std::string & s) const; | ||||
| }; | ||||
| 
 | ||||
| struct PublicKey : Key | ||||
| { | ||||
|     PublicKey(const std::string & data); | ||||
| }; | ||||
| 
 | ||||
| typedef std::map<std::string, PublicKey> PublicKeys; | ||||
| 
 | ||||
| /* Return true iff ‘sig’ is a correct signature over ‘data’ using one
 | ||||
|    of the given public keys. */ | ||||
| bool verifyDetached(const std::string & data, const std::string & sig, | ||||
|     const PublicKeys & publicKeys); | ||||
| 
 | ||||
| } | ||||
|  | @ -8,7 +8,7 @@ libstore_SOURCES := $(wildcard $(d)/*.cc) | |||
| 
 | ||||
| libstore_LIBS = libutil libformat | ||||
| 
 | ||||
| libstore_LDFLAGS = $(SQLITE3_LIBS) -lbz2 $(LIBCURL_LIBS) | ||||
| libstore_LDFLAGS = $(SQLITE3_LIBS) -lbz2 $(LIBCURL_LIBS) $(SODIUM_LIBS) | ||||
| 
 | ||||
| ifeq ($(OS), SunOS) | ||||
| 	libstore_LDFLAGS += -lsocket | ||||
|  |  | |||
							
								
								
									
										134
									
								
								src/libstore/nar-info.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/libstore/nar-info.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,134 @@ | |||
| #include "crypto.hh" | ||||
| #include "globals.hh" | ||||
| #include "nar-info.hh" | ||||
| 
 | ||||
| namespace nix { | ||||
| 
 | ||||
| NarInfo::NarInfo(const std::string & s, const std::string & whence) | ||||
| { | ||||
|     auto corrupt = [&]() { | ||||
|         throw Error("NAR info file ‘%1%’ is corrupt"); | ||||
|     }; | ||||
| 
 | ||||
|     auto parseHashField = [&](const string & s) { | ||||
|         string::size_type colon = s.find(':'); | ||||
|         if (colon == string::npos) corrupt(); | ||||
|         HashType ht = parseHashType(string(s, 0, colon)); | ||||
|         if (ht == htUnknown) corrupt(); | ||||
|         return parseHash16or32(ht, string(s, colon + 1)); | ||||
|     }; | ||||
| 
 | ||||
|     size_t pos = 0; | ||||
|     while (pos < s.size()) { | ||||
| 
 | ||||
|         size_t colon = s.find(':', pos); | ||||
|         if (colon == std::string::npos) corrupt(); | ||||
| 
 | ||||
|         std::string name(s, pos, colon - pos); | ||||
| 
 | ||||
|         size_t eol = s.find('\n', colon + 2); | ||||
|         if (eol == std::string::npos) corrupt(); | ||||
| 
 | ||||
|         std::string value(s, colon + 2, eol - colon - 2); | ||||
| 
 | ||||
|         if (name == "StorePath") { | ||||
|             if (!isStorePath(value)) corrupt(); | ||||
|             path = value; | ||||
|         } | ||||
|         else if (name == "URL") | ||||
|             url = value; | ||||
|         else if (name == "Compression") | ||||
|             compression = value; | ||||
|         else if (name == "FileHash") | ||||
|             fileHash = parseHashField(value); | ||||
|         else if (name == "FileSize") { | ||||
|             if (!string2Int(value, fileSize)) corrupt(); | ||||
|         } | ||||
|         else if (name == "NarHash") | ||||
|             narHash = parseHashField(value); | ||||
|         else if (name == "NarSize") { | ||||
|             if (!string2Int(value, narSize)) corrupt(); | ||||
|         } | ||||
|         else if (name == "References") { | ||||
|             auto refs = tokenizeString<Strings>(value, " "); | ||||
|             if (!references.empty()) corrupt(); | ||||
|             for (auto & r : refs) { | ||||
|                 auto r2 = settings.nixStore + "/" + r; | ||||
|                 if (!isStorePath(r2)) corrupt(); | ||||
|                 references.insert(r2); | ||||
|             } | ||||
|         } | ||||
|         else if (name == "Deriver") { | ||||
|             auto p = settings.nixStore + "/" + value; | ||||
|             if (!isStorePath(p)) corrupt(); | ||||
|             deriver = p; | ||||
|         } | ||||
|         else if (name == "System") | ||||
|             system = value; | ||||
|         else if (name == "Sig") | ||||
|             sig = value; | ||||
| 
 | ||||
|         pos = eol + 1; | ||||
|     } | ||||
| 
 | ||||
|     if (compression == "") compression = "bzip2"; | ||||
| 
 | ||||
|     if (path.empty() || url.empty()) corrupt(); | ||||
| } | ||||
| 
 | ||||
| std::string NarInfo::to_string() const | ||||
| { | ||||
|     std::string res; | ||||
|     res += "StorePath: " + path + "\n"; | ||||
|     res += "URL: " + url + "\n"; | ||||
|     assert(compression != ""); | ||||
|     res += "Compression: " + compression + "\n"; | ||||
|     assert(fileHash.type == htSHA256); | ||||
|     res += "FileHash: sha256:" + printHash32(fileHash) + "\n"; | ||||
|     res += "FileSize: " + std::to_string(fileSize) + "\n"; | ||||
|     assert(narHash.type == htSHA256); | ||||
|     res += "NarHash: sha256:" + printHash32(narHash) + "\n"; | ||||
|     res += "NarSize: " + std::to_string(narSize) + "\n"; | ||||
| 
 | ||||
|     res += "References: " + concatStringsSep(" ", shortRefs()) + "\n"; | ||||
| 
 | ||||
|     if (!deriver.empty()) | ||||
|         res += "Deriver: " + baseNameOf(deriver) + "\n"; | ||||
| 
 | ||||
|     if (!system.empty()) | ||||
|         res += "System: " + system + "\n"; | ||||
| 
 | ||||
|     if (!sig.empty()) | ||||
|         res += "Sig: " + sig + "\n"; | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| std::string NarInfo::fingerprint() const | ||||
| { | ||||
|     return | ||||
|         "1;" + path + ";" | ||||
|         + printHashType(narHash.type) + ":" + printHash32(narHash) + ";" | ||||
|         + std::to_string(narSize) + ";" | ||||
|         + concatStringsSep(",", references); | ||||
| } | ||||
| 
 | ||||
| Strings NarInfo::shortRefs() const | ||||
| { | ||||
|     Strings refs; | ||||
|     for (auto & r : references) | ||||
|         refs.push_back(baseNameOf(r)); | ||||
|     return refs; | ||||
| } | ||||
| 
 | ||||
| void NarInfo::sign(const SecretKey & secretKey) | ||||
| { | ||||
|     sig = secretKey.signDetached(fingerprint()); | ||||
| } | ||||
| 
 | ||||
| bool NarInfo::checkSignature(const PublicKeys & publicKeys) const | ||||
| { | ||||
|     return sig != "" && verifyDetached(fingerprint(), sig, publicKeys); | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/libstore/nar-info.hh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/libstore/nar-info.hh
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "types.hh" | ||||
| #include "hash.hh" | ||||
| #include "store-api.hh" | ||||
| 
 | ||||
| namespace nix { | ||||
| 
 | ||||
| struct NarInfo : ValidPathInfo | ||||
| { | ||||
|     std::string url; | ||||
|     std::string compression; | ||||
|     Hash fileHash; | ||||
|     uint64_t fileSize = 0; | ||||
|     std::string system; | ||||
|     std::string sig; // FIXME: support multiple signatures
 | ||||
| 
 | ||||
|     NarInfo() { } | ||||
|     NarInfo(const ValidPathInfo & info) : ValidPathInfo(info) { } | ||||
|     NarInfo(const std::string & s, const std::string & whence); | ||||
| 
 | ||||
|     std::string to_string() const; | ||||
| 
 | ||||
|     /*  Return a fingerprint of the store path to be used in binary
 | ||||
|         cache signatures. It contains the store path, the base-32 | ||||
|         SHA-256 hash of the NAR serialisation of the path, the size of | ||||
|         the NAR, and the sorted references. The size field is strictly | ||||
|         speaking superfluous, but might prevent endless/excessive data | ||||
|         attacks. */ | ||||
|     std::string fingerprint() const; | ||||
| 
 | ||||
|     void sign(const SecretKey & secretKey); | ||||
| 
 | ||||
|     /* Return true iff this .narinfo is signed by one of the specified
 | ||||
|        keys. */ | ||||
|     bool checkSignature(const PublicKeys & publicKeys) const; | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     Strings shortRefs() const; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue