about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile.config.in1
-rw-r--r--configure.ac4
-rw-r--r--perl/lib/Nix/Config.pm.in27
-rw-r--r--perl/lib/Nix/Crypto.pm42
-rw-r--r--perl/lib/Nix/Manifest.pm19
-rw-r--r--perl/lib/Nix/Store.pm1
-rw-r--r--perl/lib/Nix/Store.xs40
-rw-r--r--perl/local.mk11
-rw-r--r--release.nix4
-rwxr-xr-xscripts/nix-push.in25
-rw-r--r--src/libutil/util.cc25
-rw-r--r--src/libutil/util.hh2
-rw-r--r--src/nix-store/local.mk2
-rw-r--r--src/nix-store/nix-store.cc34
-rw-r--r--tests/binary-cache.sh50
15 files changed, 196 insertions, 91 deletions
diff --git a/Makefile.config.in b/Makefile.config.in
index 797b99f80504..5b7bf297e928 100644
--- a/Makefile.config.in
+++ b/Makefile.config.in
@@ -7,6 +7,7 @@ HAVE_OPENSSL = @HAVE_OPENSSL@
 OPENSSL_LIBS = @OPENSSL_LIBS@
 PACKAGE_NAME = @PACKAGE_NAME@
 PACKAGE_VERSION = @PACKAGE_VERSION@
+SODIUM_LIBS = @SODIUM_LIBS@
 bash = @bash@
 bindir = @bindir@
 bsddiff_compat_include = @bsddiff_compat_include@
diff --git a/configure.ac b/configure.ac
index 622cf1e2045d..00bffd8b4c27 100644
--- a/configure.ac
+++ b/configure.ac
@@ -205,6 +205,10 @@ AC_CHECK_HEADERS([bzlib.h], [true],
 PKG_CHECK_MODULES([SQLITE3], [sqlite3 >= 3.6.19], [CXXFLAGS="$SQLITE3_CFLAGS $CXXFLAGS"])
 
 
+# Look for libsodium, a required dependency.
+PKG_CHECK_MODULES([SODIUM], [libsodium], [CXXFLAGS="$SODIUM_CFLAGS $CXXFLAGS"])
+
+
 # Whether to use the Boehm garbage collector.
 AC_ARG_ENABLE(gc, AC_HELP_STRING([--enable-gc],
   [enable garbage collection in the Nix expression evaluator (requires Boehm GC) [default=no]]),
diff --git a/perl/lib/Nix/Config.pm.in b/perl/lib/Nix/Config.pm.in
index bc51310e5aff..388acd2e61c0 100644
--- a/perl/lib/Nix/Config.pm.in
+++ b/perl/lib/Nix/Config.pm.in
@@ -1,5 +1,7 @@
 package Nix::Config;
 
+use MIME::Base64;
+
 $version = "@PACKAGE_VERSION@";
 
 $binDir = $ENV{"NIX_BIN_DIR"} || "@bindir@";
@@ -19,24 +21,31 @@ $useBindings = "@perlbindings@" eq "yes";
 
 %config = ();
 
+%binaryCachePublicKeys = ();
+
 sub readConfig {
     if (defined $ENV{'_NIX_OPTIONS'}) {
         foreach my $s (split '\n', $ENV{'_NIX_OPTIONS'}) {
             my ($n, $v) = split '=', $s, 2;
             $config{$n} = $v;
         }
-        return;
+    } else {
+        my $config = "$confDir/nix.conf";
+        return unless -f $config;
+
+        open CONFIG, "<$config" or die "cannot open ‘$config’";
+        while (<CONFIG>) {
+            /^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next;
+            $config{$1} = $2;
+        }
+        close CONFIG;
     }
 
-    my $config = "$confDir/nix.conf";
-    return unless -f $config;
-
-    open CONFIG, "<$config" or die "cannot open ‘$config’";
-    while (<CONFIG>) {
-        /^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next;
-        $config{$1} = $2;
+    foreach my $s (split(/ /, $config{"binary-cache-public-keys"} // "")) {
+        my ($keyName, $publicKey) = split ":", $s;
+        next unless defined $keyName && defined $publicKey;
+        $binaryCachePublicKeys{$keyName} = decode_base64($publicKey);
     }
-    close CONFIG;
 }
 
 return 1;
diff --git a/perl/lib/Nix/Crypto.pm b/perl/lib/Nix/Crypto.pm
deleted file mode 100644
index 0286e88d3d28..000000000000
--- a/perl/lib/Nix/Crypto.pm
+++ /dev/null
@@ -1,42 +0,0 @@
-package Nix::Crypto;
-
-use strict;
-use MIME::Base64;
-use Nix::Store;
-use Nix::Config;
-use IPC::Open2;
-
-our @ISA = qw(Exporter);
-our @EXPORT = qw(signString isValidSignature);
-
-sub signString {
-    my ($privateKeyFile, $s) = @_;
-    my $hash = hashString("sha256", 0, $s);
-    my ($from, $to);
-    my $pid = open2($from, $to, $Nix::Config::openssl, "rsautl", "-sign", "-inkey", $privateKeyFile);
-    print $to $hash;
-    close $to;
-    local $/ = undef;
-    my $sig = <$from>;
-    close $from;
-    waitpid($pid, 0);
-    die "$0: OpenSSL returned exit code $? while signing hash\n" if $? != 0;
-    my $sig64 = encode_base64($sig, "");
-    return $sig64;
-}
-
-sub isValidSignature {
-    my ($publicKeyFile, $sig64, $s) = @_;
-    my ($from, $to);
-    my $pid = open2($from, $to, $Nix::Config::openssl, "rsautl", "-verify", "-inkey", $publicKeyFile, "-pubin");
-    print $to decode_base64($sig64);
-    close $to;
-    my $decoded = <$from>;
-    close $from;
-    waitpid($pid, 0);
-    return 0 if $? != 0;
-    my $hash = hashString("sha256", 0, $s);
-    return $decoded eq $hash;
-}
-
-1;
diff --git a/perl/lib/Nix/Manifest.pm b/perl/lib/Nix/Manifest.pm
index 9b7e89fa42fb..ec3e48fcfbec 100644
--- a/perl/lib/Nix/Manifest.pm
+++ b/perl/lib/Nix/Manifest.pm
@@ -8,8 +8,9 @@ use Cwd;
 use File::stat;
 use File::Path;
 use Fcntl ':flock';
+use MIME::Base64;
 use Nix::Config;
-use Nix::Crypto;
+use Nix::Store;
 
 our @ISA = qw(Exporter);
 our @EXPORT = qw(readManifest writeManifest updateManifestDB addPatch deleteOldManifests parseNARInfo);
@@ -440,22 +441,20 @@ sub parseNARInfo {
         }
         my ($sigVersion, $keyName, $sig64) = split ";", $sig;
         $sigVersion //= 0;
-        if ($sigVersion != 1) {
+        if ($sigVersion != 2) {
             warn "NAR info file ‘$location’ has unsupported version $sigVersion; ignoring\n";
             return undef;
         }
         return undef unless defined $keyName && defined $sig64;
-        my $publicKeyFile = $Nix::Config::config{"binary-cache-public-key-$keyName"};
-        if (!defined $publicKeyFile) {
+
+        my $publicKey = $Nix::Config::binaryCachePublicKeys{$keyName};
+        if (!defined $publicKey) {
             warn "NAR info file ‘$location’ is signed by unknown key ‘$keyName’; ignoring\n";
             return undef;
         }
-        if (! -f $publicKeyFile) {
-            die "binary cache public key file ‘$publicKeyFile’ does not exist\n";
-            return undef;
-        }
-        if (!isValidSignature($publicKeyFile, $sig64, $signedData)) {
-            warn "NAR info file ‘$location’ has an invalid signature; ignoring\n";
+
+        if (!checkSignature($publicKey, decode_base64($sig64), $signedData)) {
+            warn "NAR info file ‘$location’ has an incorrect signature; ignoring\n";
             return undef;
         }
         $res->{signedBy} = $keyName;
diff --git a/perl/lib/Nix/Store.pm b/perl/lib/Nix/Store.pm
index 89cfaefa5fd4..233a432ee085 100644
--- a/perl/lib/Nix/Store.pm
+++ b/perl/lib/Nix/Store.pm
@@ -17,6 +17,7 @@ our @EXPORT = qw(
     queryPathFromHashPart
     topoSortPaths computeFSClosure followLinksToStorePath exportPaths importPaths
     hashPath hashFile hashString
+    signString checkSignature
     addToStore makeFixedOutputPath
     derivationFromPath
 );
diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs
index ff90616d3766..792d2f649935 100644
--- a/perl/lib/Nix/Store.xs
+++ b/perl/lib/Nix/Store.xs
@@ -11,6 +11,8 @@
 #include <misc.hh>
 #include <util.hh>
 
+#include <sodium.h>
+
 
 using namespace nix;
 
@@ -223,6 +225,44 @@ SV * hashString(char * algo, int base32, char * s)
         }
 
 
+SV * signString(SV * secretKey_, char * msg)
+    PPCODE:
+        try {
+            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)));
+        } catch (Error & e) {
+            croak(e.what());
+        }
+
+
+int checkSignature(SV * publicKey_, SV * sig_, char * msg)
+    CODE:
+        try {
+            STRLEN publicKeyLen;
+            unsigned char * publicKey = (unsigned char *) SvPV(publicKey_, publicKeyLen);
+            if (publicKeyLen != crypto_sign_PUBLICKEYBYTES)
+                throw Error("public key is not valid");
+
+            STRLEN sigLen;
+            unsigned char * sig = (unsigned char *) SvPV(sig_, sigLen);
+            if (sigLen != crypto_sign_BYTES)
+                throw Error("signature is not valid");
+
+            RETVAL = crypto_sign_verify_detached(sig, (unsigned char *) msg, strlen(msg), publicKey) == 0;
+        } catch (Error & e) {
+            croak(e.what());
+        }
+    OUTPUT:
+        RETVAL
+
+
 SV * addToStore(char * srcPath, int recursive, char * algo)
     PPCODE:
         try {
diff --git a/perl/local.mk b/perl/local.mk
index 73d8a7c9526b..132676f53341 100644
--- a/perl/local.mk
+++ b/perl/local.mk
@@ -5,8 +5,7 @@ nix_perl_sources := \
   $(d)/lib/Nix/SSH.pm \
   $(d)/lib/Nix/CopyClosure.pm \
   $(d)/lib/Nix/Config.pm.in \
-  $(d)/lib/Nix/Utils.pm \
-  $(d)/lib/Nix/Crypto.pm
+  $(d)/lib/Nix/Utils.pm
 
 nix_perl_modules := $(nix_perl_sources:.in=)
 
@@ -23,16 +22,18 @@ ifeq ($(perlbindings), yes)
 
   Store_SOURCES := $(Store_DIR)/Store.cc
 
-  Store_LIBS = libstore libutil
-
   Store_CXXFLAGS = \
     -I$(shell $(perl) -e 'use Config; print $$Config{archlibexp};')/CORE \
     -D_FILE_OFFSET_BITS=64 -Wno-unused-variable -Wno-literal-suffix -Wno-reserved-user-defined-literal
 
+  Store_LIBS = libstore libutil
+
+  Store_LDFLAGS := $(SODIUM_LIBS)
+
   ifeq (CYGWIN,$(findstring CYGWIN,$(OS)))
     archlib = $(shell perl -E 'use Config; print $$Config{archlib};')
     libperl = $(shell perl -E 'use Config; print $$Config{libperl};')
-    Store_LDFLAGS = $(shell find ${archlib} -name ${libperl})
+    Store_LDFLAGS += $(shell find ${archlib} -name ${libperl})
   endif
 
   Store_ALLOW_UNDEFINED = 1
diff --git a/release.nix b/release.nix
index a08cf7a96c81..5b6ff22844fc 100644
--- a/release.nix
+++ b/release.nix
@@ -24,7 +24,7 @@ let
 
         buildInputs =
           [ curl bison flex perl libxml2 libxslt bzip2
-            tetex dblatex nukeReferences pkgconfig sqlite
+            tetex dblatex nukeReferences pkgconfig sqlite libsodium
             docbook5 docbook5_xsl
           ] ++ lib.optional (!lib.inNixShell) git;
 
@@ -80,7 +80,7 @@ let
         name = "nix";
         src = tarball;
 
-        buildInputs = [ curl perl bzip2 openssl pkgconfig sqlite boehmgc ];
+        buildInputs = [ curl perl bzip2 openssl pkgconfig sqlite boehmgc libsodium ];
 
         configureFlags = ''
           --disable-init-state
diff --git a/scripts/nix-push.in b/scripts/nix-push.in
index c6d187704bc7..0e90ab3c216b 100755
--- a/scripts/nix-push.in
+++ b/scripts/nix-push.in
@@ -6,11 +6,11 @@ use File::Basename;
 use File::Path qw(mkpath);
 use File::stat;
 use File::Copy;
+use MIME::Base64;
 use Nix::Config;
 use Nix::Store;
 use Nix::Manifest;
 use Nix::Utils;
-use Nix::Crypto;
 
 binmode STDERR, ":encoding(utf8)";
 
@@ -27,8 +27,7 @@ my $writeManifest = 0;
 my $manifestPath;
 my $archivesURL;
 my $link = 0;
-my $privateKeyFile;
-my $keyName;
+my $secretKeyFile;
 my @roots;
 
 for (my $n = 0; $n < scalar @ARGV; $n++) {
@@ -61,14 +60,10 @@ for (my $n = 0; $n < scalar @ARGV; $n++) {
         $archivesURL = $ARGV[$n];
     } elsif ($arg eq "--link") {
         $link = 1;
-    } elsif ($arg eq "--key") {
+    } elsif ($arg eq "--key-file") {
         $n++;
         die "$0: ‘$arg’ requires an argument\n" unless $n < scalar @ARGV;
-        $privateKeyFile = $ARGV[$n];
-    } elsif ($arg eq "--key-name") {
-        $n++;
-        die "$0: ‘$arg’ requires an argument\n" unless $n < scalar @ARGV;
-        $keyName = $ARGV[$n];
+        $secretKeyFile = $ARGV[$n];
     } elsif (substr($arg, 0, 1) eq "-") {
         die "$0: unknown flag ‘$arg’\n";
     } else {
@@ -110,7 +105,7 @@ my %narFiles;
 foreach my $storePath (@storePaths) {
     my $pathHash = substr(basename($storePath), 0, 32);
     my $narInfoFile = "$destDir/$pathHash.narinfo";
-    if (-e $narInfoFile) {
+    if (!$force && -e $narInfoFile) {
         my $narInfo = parseNARInfo($storePath, readFile($narInfoFile), 0, $narInfoFile) or die "cannot read ‘$narInfoFile’\n";
         my $narFile = "$destDir/$narInfo->{url}";
         if (-e $narFile) {
@@ -257,9 +252,13 @@ for (my $n = 0; $n < scalar @storePaths2; $n++) {
         }
     }
 
-    if (defined $privateKeyFile && defined $keyName) {
-        my $sig = signString($privateKeyFile, $info);
-        $info .= "Signature: 1;$keyName;$sig\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 $sig = encode_base64(signString(decode_base64($secretKey), $info), "");
+        $info .= "Signature: 2;$keyName;$sig\n";
     }
 
     my $pathHash = substr(basename($storePath), 0, 32);
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 0d903f2f0d43..4f3010880286 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -925,18 +925,24 @@ std::vector<const char *> stringsToCharPtrs(const Strings & ss)
 }
 
 
-string runProgram(Path program, bool searchPath, const Strings & args)
+string runProgram(Path program, bool searchPath, const Strings & args,
+    const string & input)
 {
     checkInterrupt();
 
     /* Create a pipe. */
-    Pipe pipe;
-    pipe.create();
+    Pipe stdout, stdin;
+    stdout.create();
+    if (!input.empty()) stdin.create();
 
     /* Fork. */
     Pid pid = startProcess([&]() {
-        if (dup2(pipe.writeSide, STDOUT_FILENO) == -1)
+        if (dup2(stdout.writeSide, STDOUT_FILENO) == -1)
             throw SysError("dupping stdout");
+        if (!input.empty()) {
+            if (dup2(stdin.readSide, STDIN_FILENO) == -1)
+                throw SysError("dupping stdin");
+        }
 
         Strings args_(args);
         args_.push_front(program);
@@ -950,9 +956,16 @@ string runProgram(Path program, bool searchPath, const Strings & args)
         throw SysError(format("executing ‘%1%’") % program);
     });
 
-    pipe.writeSide.close();
+    stdout.writeSide.close();
+
+    /* FIXME: This can deadlock if the input is too long. */
+    if (!input.empty()) {
+        stdin.readSide.close();
+        writeFull(stdin.writeSide, input);
+        stdin.writeSide.close();
+    }
 
-    string result = drainFD(pipe.readSide);
+    string result = drainFD(stdout.readSide);
 
     /* Wait for the child to finish. */
     int status = pid.wait(true);
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 186ee71f45d0..1a2dda527121 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -285,7 +285,7 @@ pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = P
 /* Run a program and return its stdout in a string (i.e., like the
    shell backtick operator). */
 string runProgram(Path program, bool searchPath = false,
-    const Strings & args = Strings());
+    const Strings & args = Strings(), const string & input = "");
 
 MakeError(ExecError, Error)
 
diff --git a/src/nix-store/local.mk b/src/nix-store/local.mk
index b887fe03389b..84ff15b241f3 100644
--- a/src/nix-store/local.mk
+++ b/src/nix-store/local.mk
@@ -6,6 +6,6 @@ nix-store_SOURCES := $(wildcard $(d)/*.cc)
 
 nix-store_LIBS = libmain libstore libutil libformat
 
-nix-store_LDFLAGS = -lbz2 -pthread
+nix-store_LDFLAGS = -lbz2 -pthread $(SODIUM_LIBS)
 
 nix-store_CXXFLAGS = -DCURL=\"$(curl)\"
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index 87bc8c379de5..18739736838e 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -20,6 +20,8 @@
 
 #include <bzlib.h>
 
+#include <sodium.h>
+
 
 using namespace nix;
 using std::cin;
@@ -1006,6 +1008,32 @@ static void opServe(Strings opFlags, Strings opArgs)
 }
 
 
+static void opGenerateBinaryCacheKey(Strings opFlags, Strings opArgs)
+{
+    foreach (Strings::iterator, i, opFlags)
+        throw UsageError(format("unknown flag ‘%1%’") % *i);
+
+    if (opArgs.size() != 1) throw UsageError("one argument expected");
+    string keyName = opArgs.front();
+
+    sodium_init();
+
+    unsigned char pk[crypto_sign_PUBLICKEYBYTES];
+    unsigned char sk[crypto_sign_SECRETKEYBYTES];
+    if (crypto_sign_keypair(pk, sk) != 0)
+        throw Error("key generation failed");
+
+    // FIXME: super ugly way to do base64 encoding.
+    auto args = Strings({"-MMIME::Base64", "-0777", "-ne", "print encode_base64($_, '')"});
+
+    string pk64 = runProgram("perl", true, args, string((char *) pk, crypto_sign_PUBLICKEYBYTES));
+    std::cout << keyName << ":" << pk64 << std::endl;
+
+    string sk64 = runProgram("perl", true, args, string((char *) sk, crypto_sign_SECRETKEYBYTES));
+    std::cout << keyName << ":" << sk64 << std::endl;
+}
+
+
 /* Scan the arguments; find the operation, set global flags, put all
    other flags in a list, and put all other arguments in another
    list. */
@@ -1072,14 +1100,16 @@ int main(int argc, char * * argv)
                 op = opQueryFailedPaths;
             else if (*arg == "--clear-failed-paths")
                 op = opClearFailedPaths;
+            else if (*arg == "--serve")
+                op = opServe;
+            else if (*arg == "--generate-binary-cache-key")
+                op = opGenerateBinaryCacheKey;
             else if (*arg == "--add-root")
                 gcRoot = absPath(getArg(*arg, arg, end));
             else if (*arg == "--indirect")
                 indirectRoot = true;
             else if (*arg == "--no-output")
                 noOutput = true;
-            else if (*arg == "--serve")
-                op = opServe;
             else if (*arg != "" && arg->at(0) == '-') {
                 opFlags.push_back(*arg);
                 if (*arg == "--max-freed" || *arg == "--max-links" || *arg == "--max-atime") /* !!! hack */
diff --git a/tests/binary-cache.sh b/tests/binary-cache.sh
index 6f0c36f6300f..b0e7f63ae0b6 100644
--- a/tests/binary-cache.sh
+++ b/tests/binary-cache.sh
@@ -87,3 +87,53 @@ rm $(grep -l "StorePath:.*dependencies-input-2" $cacheDir/*.narinfo)
 
 nix-build --option binary-caches "file://$cacheDir" dependencies.nix -o $TEST_ROOT/result 2>&1 | tee $TEST_ROOT/log
 grep -q "Downloading" $TEST_ROOT/log
+
+
+# Create a signed binary cache.
+clearCache
+
+declare -a res=($(nix-store --generate-binary-cache-key test.nixos.org-1))
+publicKey="${res[0]}"
+secretKey="${res[1]}"
+echo "$secretKey" > $TEST_ROOT/secret-key
+
+res=($(nix-store --generate-binary-cache-key test.nixos.org-1))
+badKey="${res[0]}"
+
+res=($(nix-store --generate-binary-cache-key foo.nixos.org-1))
+otherKey="${res[0]}"
+
+nix-push --dest $cacheDir --key-file $TEST_ROOT/secret-key $outPath
+
+
+# Downloading should fail if we don't provide a key.
+clearStore
+
+rm -f $NIX_STATE_DIR/binary-cache*
+
+(! nix-store -r $outPath --option binary-caches "file://$cacheDir" --option signed-binary-caches '*' )
+
+
+# And it should fail if we provide an incorrect key.
+clearStore
+
+rm -f $NIX_STATE_DIR/binary-cache*
+
+(! nix-store -r $outPath --option binary-caches "file://$cacheDir" --option signed-binary-caches '*' --option binary-cache-public-keys "$badKey")
+
+
+# It should succeed if we provide the correct key.
+nix-store -r $outPath --option binary-caches "file://$cacheDir" --option signed-binary-caches '*' --option binary-cache-public-keys "$otherKey $publicKey"
+
+
+# It should fail if we corrupt the .narinfo.
+clearStore
+
+for i in $cacheDir/*.narinfo; do
+    grep -v References $i > $i.tmp
+    mv $i.tmp $i
+done
+
+rm -f $NIX_STATE_DIR/binary-cache*
+
+(! nix-store -r $outPath --option binary-caches "file://$cacheDir" --option signed-binary-caches '*' --option binary-cache-public-keys "$publicKey")