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 "globals.hh" | ||||||
| #include "store-api.hh" | #include "store-api.hh" | ||||||
| #include "util.hh" | #include "util.hh" | ||||||
|  | #include "crypto.hh" | ||||||
| 
 | 
 | ||||||
| #if HAVE_SODIUM | #if HAVE_SODIUM | ||||||
| #include <sodium.h> | #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: |     PPCODE: | ||||||
|         try { |         try { | ||||||
| #if HAVE_SODIUM | #if HAVE_SODIUM | ||||||
|             STRLEN secretKeyLen; |             auto sig = SecretKey(secretKey_).signDetached(msg); | ||||||
|             unsigned char * secretKey = (unsigned char *) SvPV(secretKey_, secretKeyLen); |             XPUSHs(sv_2mortal(newSVpv(sig.c_str(), sig.size()))); | ||||||
|             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))); |  | ||||||
| #else | #else | ||||||
|             throw Error("Nix was not compiled with libsodium, required for signed binary cache support"); |             throw Error("Nix was not compiled with libsodium, required for signed binary cache support"); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -258,13 +258,10 @@ for (my $n = 0; $n < scalar @storePaths2; $n++) { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (defined $secretKeyFile) { |     if (defined $secretKeyFile) { | ||||||
|         my $s = readFile $secretKeyFile; |         my $secretKey = readFile $secretKeyFile; | ||||||
|         chomp $s; |  | ||||||
|         my ($keyName, $secretKey) = split ":", $s; |  | ||||||
|         die "invalid secret key file ‘$secretKeyFile’\n" unless defined $keyName && defined $secretKey; |  | ||||||
|         my $fingerprint = fingerprintPath($storePath, $narHash, $narSize, $refs); |         my $fingerprint = fingerprintPath($storePath, $narHash, $narSize, $refs); | ||||||
|         my $sig = encode_base64(signString(decode_base64($secretKey), $fingerprint), ""); |         my $sig = signString($secretKey, $fingerprint); | ||||||
|         $info .= "Sig: $keyName:$sig\n"; |         $info .= "Sig: $sig\n"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     my $pathHash = substr(basename($storePath), 0, 32); |     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_LIBS = libutil libformat | ||||||
| 
 | 
 | ||||||
| libstore_LDFLAGS = $(SQLITE3_LIBS) -lbz2 $(LIBCURL_LIBS) | libstore_LDFLAGS = $(SQLITE3_LIBS) -lbz2 $(LIBCURL_LIBS) $(SODIUM_LIBS) | ||||||
| 
 | 
 | ||||||
| ifeq ($(OS), SunOS) | ifeq ($(OS), SunOS) | ||||||
| 	libstore_LDFLAGS += -lsocket | 	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