Move replay check to operations list handlers
authorJacob Bachmeyer <jcb@gnu.org>
Fri, 8 Sep 2023 23:38:00 +0000 (18:38 -0500)
committerJacob Bachmeyer <jcb@gnu.org>
Fri, 8 Sep 2023 23:38:00 +0000 (18:38 -0500)
This also splits the test into separate check and update steps, such that
the timestamp ratchet file is no longer updated when a packet is rejected.

gatekeeper.pl
testsuite/gatekeeper.all/03_triplet.exp
testsuite/lib/tool/gatekeeper.exp

index edd205a61a3c52962b4a57c36402879e6b857655..4ee13da116be9c1f9b9d01d4db3cedf9fa646bce 100755 (executable)
@@ -1722,11 +1722,11 @@ Install a file into the managed tree as DESTINATION_FILENAME.
     my $info = shift;
     my $packet = $info->{packet};
 
+    my $install_as = $step->[1];
+
     # If the upload installs a file, check if the final file exists; if so,
     # require the 'replace' option to be set.
 
-    my $install_as = $step->[1];
-
     my $pubfinal = File::Spec::Unix->catfile
       (pub => @{$packet->target_directory}, $install_as);
     my $final_upload = File::Spec->catfile
@@ -1740,6 +1740,13 @@ Install a file into the managed tree as DESTINATION_FILENAME.
          summary => $pubfinal." exists and 'replace' was not selected";
       }
     }
+
+    # If a file is to be installed, ensure that this directive is newer than
+    # any previous directive installing a file under the same full name.
+
+    ::check_timestamp_ratchet
+      (File::Spec::Unix->catfile($packet->target_directory, $install_as),
+       $packet->auth_signature_timestamp);
   }
 
   sub execute {
@@ -1765,6 +1772,10 @@ Install a file into the managed tree as DESTINATION_FILENAME.
 
     mkdir_p CONF_DIR_Public, @$directory;
 
+    ::advance_timestamp_ratchet
+      (File::Spec::Unix->catfile($packet->target_directory, $install_as),
+       $packet->auth_signature_timestamp);
+
     # We now allow overwriting of files - without warning!!
     if (-e $final_signature || -e $final_upload) {
       # previous validation has ensured that the 'replace' option is set
@@ -1970,6 +1981,7 @@ BEGIN {
 
   sub auth_keyrings;
   sub auth_signature_fingerprints;
+  sub auth_signature_timestamp;
 
   sub parse;
   sub auth_check;
@@ -2070,8 +2082,6 @@ BEGIN {
       summary => "gpg verification problem: could not extract timestamp"
        unless defined $dsig_info->{sig_creation};
     check_signature_timestamp(directive => $dsig_info->{sig_creation});
-
-    ::check_replay($self->{oplist}, $dsig_info->{sig_creation});
   }
 
   sub auth_signature_fingerprints {
@@ -2083,6 +2093,15 @@ BEGIN {
     return $self->{auth_directive_signature_info}{key_fingerprint};
   }
 
+  sub auth_signature_timestamp {
+    my $self = shift;
+
+    return ()
+      unless $self->{auth_directive_signature_info}
+       && $self->{auth_directive_signature_info}{sig_creation};
+    return $self->{auth_directive_signature_info}{sig_creation};
+  }
+
   sub upload_check { }
 
   sub install {
@@ -3145,6 +3164,56 @@ sub advance_timestamp_ratchet {
   return $old_epoch;
 }
 
+=item $epoch = check_timestamp_ratchet ( $full_filename, $epoch )
+
+Locate the stored timestamp for FULL_FILENAME, which is relative to the
+root of the managed tree, and verify that the given EPOCH is newer.  Return
+the stored timestamp.
+
+An exception is thrown if the given EPOCH timestamp is not newer than the
+stored value. An undefined value is returned if FULL_FILENAME did not have
+a stored timestamp.
+
+=cut
+
+sub check_timestamp_ratchet {
+  my $full_filename = shift;
+  my $new_epoch = shift;
+
+  my $serials_path = File::Spec->catfile(CONF_DIR_State, 'serials');
+  my $serials_flag_name = File::Spec->catfile(CONF_DIR_State, 'serials.flag');
+
+  open my $serials_flag, '>', $serials_flag_name
+    or die "open serials flag: $!";
+  flock $serials_flag, LOCK_EX
+    or die "lock serials flag: $!";
+
+  return undef unless -e $serials_path;
+
+  my $old_epoch = undef;
+
+  open my $serials, '<', $serials_path or die "open $serials_path: $!";
+  flock $serials, LOCK_EX;
+  local *_;
+  while (<$serials>) {
+    s/\s+//g;
+    m/^(.*?):(.*?)$/
+      or abort "bad line in serials file: [$_]";
+    $old_epoch = $2 if $1 eq $full_filename;
+  }
+  flock $serials, LOCK_UN      or die "unlock serials: $!";
+  close $serials               or die "close serials: $!";
+  flock $serials_flag, LOCK_UN or die "unlock serials flag: $!";
+  close $serials_flag          or die "close serials flag: $!";
+
+  if (defined $old_epoch and $old_epoch >= $new_epoch) {
+    throw signature_replay =>
+      previous_timestamp => $old_epoch, new_timestamp => $new_epoch
+  }
+
+  return $old_epoch;
+}
+
 =item check_signature_timestamp ( $what , $timestamp )
 
 Report the WHAT signature TIMESTAMP to the log and raise an exception if
@@ -3167,35 +3236,6 @@ sub check_signature_timestamp {
   }
 }
 
-=item check_replay ( $oplist, $timestamp )
-
-Check that OPLIST has not been seen before.  This is accomplished by
-storing directive signature timestamps, indexed by the name of the
-published file they installed.  The TIMESTAMP is the signature creation
-timestamp obtained from C<verify_clearsigned_message> for this directive.
-
-An exception is thrown if this directive is not the newest we have seen for
-the file it seeks to install.
-
-=cut
-
-sub check_replay {
-  my $ops = shift;
-  my $timestamp = shift;
-
-  my $op_header = $ops->[0][1];
-
-  # If a file is to be installed, ensure that this directive is newer than
-  # any previous directive installing a file under the same full name.
-  if (grep $_->[0] eq 'install', @$ops) {
-    foreach my $installed (map $_->[1], grep $_->[0] eq 'install', @$ops) {
-      my $full_filename = File::Spec::Unix->catfile($op_header->{directory},
-                                                   $installed);
-      advance_timestamp_ratchet($full_filename, $timestamp);
-    }
-  }
-}
-
 \f
 
 =back
index f8bec87f5becbecf5fad296d344e424230c97136..f9df22bcb6c71cbc68ed786948480fce19b1922b 100644 (file)
@@ -1407,7 +1407,6 @@ foreach FVER $DIRECTIVE_FORMAT_VERSIONS {
            file { test } fsig { good 0B 1000 }
        }
     }] check {
-       serials updated
        file-tree {
            { inbox stage archive } empty {}
            { scratch } files {
@@ -1451,7 +1450,6 @@ foreach FVER $DIRECTIVE_FORMAT_VERSIONS {
            file { test } fsig { good 0B 1000 }
        }
     }] check {
-       serials updated
        file-tree {
            { inbox stage archive } empty {}
            { scratch } files {
index 3d8c2082019730d3472bcd82984a8d405417fe06..1a57962f2717f6b42ff9853f02c230cba3340662 100644 (file)
@@ -631,7 +631,7 @@ proc analyze_log { base_dir name assess } {
                     set A(validate,future-signature-timestamp) 1
                     exp_continue
                 }
-       -re {^gatekeeper\[[0-9]+\]: \(Test\) \[AA\]\
+       -re {^gatekeeper\[[0-9]+\]: \(Test\) \[EX\]\
                 directive signature timestamp older than expected} {
                     # from read_directive_file, if signature timestamp bad
                     set A(validate,older-signature-timestamp) 1