Sign a subset of the .narinfo

We only need to sign the store path, NAR hash and references (the
"fingerprint"). Everything else is irrelevant to security. For
instance, the compression algorithm or the hash of the compressed NAR
don't matter as long as the contents of the uncompressed NAR are
correct.

(Maybe we should include derivers in the fingerprint, but they're
broken and nobody cares about them. Also, it might be nice in the
future if .narinfos contained signatures from multiple independent
signers. But that's impossible if the deriver is included in the
fingerprint, since everybody will tend to have a different deriver for
the same store path.)

Also renamed the "Signature" field to "Sig" since the format changed
in an incompatible way.
This commit is contained in:
Eelco Dolstra 2015-02-04 17:59:31 +01:00
parent e0def5bc4b
commit f3a5930488
2 changed files with 29 additions and 13 deletions

View file

@ -13,7 +13,7 @@ use Nix::Config;
use Nix::Store; use Nix::Store;
our @ISA = qw(Exporter); our @ISA = qw(Exporter);
our @EXPORT = qw(readManifest writeManifest updateManifestDB addPatch deleteOldManifests parseNARInfo); our @EXPORT = qw(readManifest writeManifest updateManifestDB addPatch deleteOldManifests parseNARInfo fingerprintPath);
sub addNAR { sub addNAR {
@ -395,12 +395,26 @@ sub deleteOldManifests {
} }
# Return a fingerprint of a store path to be used in binary cache
# signatures. It contains the store path, the SHA-256 hash of the
# contents of the path, and the references.
sub fingerprintPath {
my ($storePath, $narHash, $references) = @_;
die if substr($storePath, 0, length($Nix::Config::storeDir)) ne $Nix::Config::storeDir;
die if substr($narHash, 0, 7) ne "sha256:";
die if length($narHash) != 59;
foreach my $ref (@{$references}) {
die if substr($ref, 0, length($Nix::Config::storeDir)) ne $Nix::Config::storeDir;
}
return "1;" . $storePath . ";" . $narHash . ";" . join(",", @{$references});
}
# Parse a NAR info file. # Parse a NAR info file.
sub parseNARInfo { sub parseNARInfo {
my ($storePath, $content, $requireValidSig, $location) = @_; my ($storePath, $content, $requireValidSig, $location) = @_;
my ($storePath2, $url, $fileHash, $fileSize, $narHash, $narSize, $deriver, $system, $sig); my ($storePath2, $url, $fileHash, $fileSize, $narHash, $narSize, $deriver, $system, $sig);
my $signedData = "";
my $compression = "bzip2"; my $compression = "bzip2";
my @refs; my @refs;
@ -416,8 +430,7 @@ sub parseNARInfo {
elsif ($1 eq "References") { @refs = split / /, $2; } elsif ($1 eq "References") { @refs = split / /, $2; }
elsif ($1 eq "Deriver") { $deriver = $2; } elsif ($1 eq "Deriver") { $deriver = $2; }
elsif ($1 eq "System") { $system = $2; } elsif ($1 eq "System") { $system = $2; }
elsif ($1 eq "Signature") { $sig = $2; last; } elsif ($1 eq "Sig") { $sig = $2; }
$signedData .= "$line\n";
} }
return undef if $storePath ne $storePath2 || !defined $url || !defined $narHash; return undef if $storePath ne $storePath2 || !defined $url || !defined $narHash;
@ -435,16 +448,13 @@ sub parseNARInfo {
}; };
if ($requireValidSig) { if ($requireValidSig) {
# FIXME: might be useful to support multiple signatures per .narinfo.
if (!defined $sig) { if (!defined $sig) {
warn "NAR info file $location lacks a signature; ignoring\n"; warn "NAR info file $location lacks a signature; ignoring\n";
return undef; return undef;
} }
my ($sigVersion, $keyName, $sig64) = split ";", $sig; my ($keyName, $sig64) = split ":", $sig;
$sigVersion //= 0;
if ($sigVersion != 2) {
warn "NAR info file $location has unsupported version $sigVersion; ignoring\n";
return undef;
}
return undef unless defined $keyName && defined $sig64; return undef unless defined $keyName && defined $sig64;
my $publicKey = $Nix::Config::binaryCachePublicKeys{$keyName}; my $publicKey = $Nix::Config::binaryCachePublicKeys{$keyName};
@ -453,10 +463,15 @@ sub parseNARInfo {
return undef; return undef;
} }
if (!checkSignature($publicKey, decode_base64($sig64), $signedData)) { my $fingerprint = fingerprintPath(
$storePath, $narHash,
[ map { "$Nix::Config::storeDir/$_" } @refs ]);
if (!checkSignature($publicKey, decode_base64($sig64), $fingerprint)) {
warn "NAR info file $location has an incorrect signature; ignoring\n"; warn "NAR info file $location has an incorrect signature; ignoring\n";
return undef; return undef;
} }
$res->{signedBy} = $keyName; $res->{signedBy} = $keyName;
} }

View file

@ -257,8 +257,9 @@ for (my $n = 0; $n < scalar @storePaths2; $n++) {
chomp $s; chomp $s;
my ($keyName, $secretKey) = split ":", $s; my ($keyName, $secretKey) = split ":", $s;
die "invalid secret key file $secretKeyFile\n" unless defined $keyName && defined $secretKey; die "invalid secret key file $secretKeyFile\n" unless defined $keyName && defined $secretKey;
my $sig = encode_base64(signString(decode_base64($secretKey), $info), ""); my $fingerprint = fingerprintPath($storePath, $narHash, $refs);
$info .= "Signature: 2;$keyName;$sig\n"; my $sig = encode_base64(signString(decode_base64($secretKey), $fingerprint), "");
$info .= "Sig: $keyName:$sig\n";
} }
my $pathHash = substr(basename($storePath), 0, 32); my $pathHash = substr(basename($storePath), 0, 32);