about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2015-02-04T15·43+0100
committerEelco Dolstra <eelco.dolstra@logicblox.com>2015-02-04T16·10+0100
commite0def5bc4b41ad09ce3f188bf522814ef3389e1f (patch)
tree70b894e41d8b682a166872d28d720e438aea8dda
parent0d1dafa0c4ef8adc27315653df8a170c0cf33985 (diff)
Use libsodium instead of OpenSSL for binary cache signing
Sodium's Ed25519 signatures are much shorter than OpenSSL's RSA
signatures. Public keys are also much shorter, so they're now
specified directly in the nix.conf option ‘binary-cache-public-keys’.

The new command ‘nix-store --generate-binary-cache-key’ generates and
prints a public and secret key.
-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 797b99f805..5b7bf297e9 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 622cf1e204..00bffd8b4c 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 bc51310e5a..388acd2e61 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 0286e88d3d..0000000000
--- 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 9b7e89fa42..ec3e48fcfb 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 89cfaefa5f..233a432ee0 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 ff90616d37..792d2f6499 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 73d8a7c952..132676f533 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 a08cf7a96c..5b6ff22844 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 c6d187704b..0e90ab3c21 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 0d903f2f0d..4f30108802 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 186ee71f45..1a2dda5271 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 b887fe0338..84ff15b241 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 87bc8c379d..1873973683 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 6f0c36f630..b0e7f63ae0 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")