Factor analysis out of verify_clearsigned_message
authorJacob Bachmeyer <jcb@gnu.org>
Sat, 12 Nov 2022 02:28:16 +0000 (20:28 -0600)
committerJacob Bachmeyer <jcb@gnu.org>
Sat, 12 Nov 2022 02:28:16 +0000 (20:28 -0600)
This is in preparation for also using --status-fd when verifying detached
signatures for uploaded files.

gatekeeper.pl

index e57e5fcbad32864391eaf079abfafba812df560e..ac15d79ab66b07a9d6f285927fe5b101dc383b23 100755 (executable)
@@ -801,6 +801,102 @@ values are untainted.  The C<TILT> field, if present, is untainted.
 
 =cut
 
+# helper for verify_clearsigned_message
+sub _analyze_gpgv_output {
+  my $ret = shift;     # hashref
+
+  # CVE-2022-34903 caused GPG to dump a chunk of its heap to the status fd,
+  # and, eventually, segfault upon reaching unallocated address space.
+  # This had two recognizable consequences:
+  #  - The GPG process dies with SIGSEGV.
+  #  - The status output very likely contains multiple NUL bytes.
+  push @{$ret->{TILT}}, 'gpgv died on signal '.WTERMSIG($ret->{exitcode})
+    if WIFSIGNALED($ret->{exitcode});
+  for (qw(output log status))
+    { push @{$ret->{TILT}}, "gpgv $_ contained NUL byte"
+       if $ret->{'raw_'.$_} =~ m/\0/ }
+
+  local *_;
+  # counters
+  my $intro_status = 0; my $check_status = 0; my $verdict_status = 0;
+
+  open my $status, '<', \($ret->{raw_status})
+    or ftp_abort('open in-memory file for gpgv status');
+  while (<$status>) {
+    chomp;
+    unless (m/^\[GNUPG:\] /g) {
+      push @{$ret->{TILT}}, "gpgv status line lacks required prefix";
+      last;    # stop parsing if an invalid line is found
+    }
+
+    if (m/\GNEWSIG/gc) {
+      $intro_status++;         # Note that NEWSIG is optional
+    } elsif (m/\G(GOOD|EXP|EXPKEY|REVKEY|BAD|ERR)SIG ([[:xdigit:]]+) /gc) {
+      #  $1 -- result tag               $2 -- long ID or fingerprint
+      # The next field is the primary username, except ERRSIG, but there is
+      # no guarantee that the primary UID will contain an email address.
+      if (length($2) > 16) {   # We have a key fingerprint
+       $ret->{key_fingerprint} = $2;
+       $ret->{key_longid} = substr $2,-16;
+      } else {                 # We only have a long key ID
+       $ret->{key_longid} = $2;
+      }
+
+      if ($1 eq 'BAD') {
+       $verdict_status++;
+       push @{$ret->{TILT}}, 'gpgv reported a bad signature, but exited zero'
+         if 0 == $ret->{exitcode};
+      } elsif ($1 eq 'ERR') {          # an ERRSIG line
+       $verdict_status++;
+       if (m/\G(\d+)\s(\d+)\s([[:xdigit:]]{2})\s([-:T[:digit:]Z+]+)\s(\d+)
+            /gcx) {
+       #  $1 -- pubkey algorithm        $2 -- digest algorithm
+       #  $3 -- timestamp               $4 -- result code
+         ftp_abort('gpgv returned an ISO8601 timestamp; implementation needed')
+           if $3 =~ m/T/;
+         $ret->{sig_creation} = $3;
+       } else
+         { push @{$ret->{TILT}}, 'gpgv ERRSIG line failed parsing' }
+
+       push @{$ret->{TILT}}, 'gpgv reported an error, but exited zero'
+         if 0 == $ret->{exitcode};
+      } else {                         # GOODSIG/EXPSIG/EXPKEYSIG/REVKEYSIG
+       $check_status++;
+      }
+    } elsif (m/\G(VALID)SIG\s([[:xdigit:]]+)\s(\d{4}-\d{2}-\d{2})\s
+              ([-:T[:digit:]Z+]+)\s([-:T[:digit:]Z+]+)\s(\d+)\s(\S+)\s
+              (\d+)\s(\d+)\s([[:xdigit:]]{2})\s([[:xdigit:]]+)
+             /gcx) {
+      $verdict_status++;
+      #  $1 -- valid tag                $2 -- key fingerprint
+      #  $3 -- signature date           $4 -- signature timestamp
+      #  $5 -- expiration timestamp     $6 -- signature version
+      #  $7 -- reserved                         $8 -- pubkey algorithm
+      #  $9 -- digest algorithm                $10 -- signature class
+      # $11 -- primary key fingerprint
+      $ret->{key_fingerprint} = $2;
+      $ret->{key_longid} = substr $2,-16;
+      ftp_abort('gpgv returned an ISO8601 timestamp; implementation needed')
+       if $4 =~ m/T/ || $5 =~ m/T/;
+      $ret->{sig_creation} = $4;
+      # GPG reports 0 if the signature does not expire
+      $ret->{sig_expiration} = $5 if $5 > 0;
+    }
+  }
+  close $status or ftp_abort('close in-memory file for gpgv status');
+
+  push @{$ret->{TILT}}, 'gpgv reported more than one signature'
+    if $intro_status > 1;
+  push @{$ret->{TILT}}, 'gpgv reported more than one signature check'
+    if $check_status > 1;
+  push @{$ret->{TILT}}, 'gpgv reported more than one signature verdict'
+    if $verdict_status > 1;
+  push @{$ret->{TILT}}, 'gpgv reported no signature verdict at all'
+    if $verdict_status < 1;
+
+  return $ret;
+}
+
 sub verify_clearsigned_message {
   my $text = shift;
   my @keyrings = @_;
@@ -950,96 +1046,7 @@ sub verify_clearsigned_message {
   my %ret = (exitcode => $?, raw_output => $raw_output,
             raw_log => $raw_log, raw_status => $raw_status);
 
-  # Analyze the results
-
-  # CVE-2022-34903 caused GPG to dump a chunk of its heap to the status fd,
-  # and, eventually, segfault upon reaching unallocated address space.
-  # This had two recognizable consequences:
-  #  - The GPG process dies with SIGSEGV.
-  #  - The status output very likely contains multiple NUL bytes.
-  push @{$ret{TILT}}, 'gpgv died on signal '.WTERMSIG($ret{exitcode})
-    if WIFSIGNALED($ret{exitcode});
-  for (qw(output log status))
-    { push @{$ret{TILT}}, "gpgv $_ contained NUL byte"
-       if $ret{'raw_'.$_} =~ m/\0/ }
-
-  local *_;
-  # counters
-  my $intro_status = 0; my $check_status = 0; my $verdict_status = 0;
-
-  open my $status, '<', \$ret{raw_status}
-    or ftp_abort('open in-memory file for gpgv status');
-  while (<$status>) {
-    chomp;
-    unless (m/^\[GNUPG:\] /g) {
-      push @{$ret{TILT}}, "gpgv status line lacks required prefix";
-      last;    # stop parsing if an invalid line is found
-    }
-
-    if (m/\GNEWSIG/gc) {
-      $intro_status++;         # Note that NEWSIG is optional
-    } elsif (m/\G(GOOD|EXP|EXPKEY|REVKEY|BAD|ERR)SIG ([[:xdigit:]]+) /gc) {
-      #  $1 -- result tag               $2 -- long ID or fingerprint
-      # The next field is the primary username, except ERRSIG, but there is
-      # no guarantee that the primary UID will contain an email address.
-      if (length($2) > 16) {   # We have a key fingerprint
-       $ret{key_fingerprint} = $2;
-       $ret{key_longid} = substr $2,-16;
-      } else {                 # We only have a long key ID
-       $ret{key_longid} = $2;
-      }
-
-      if ($1 eq 'BAD') {
-       $verdict_status++;
-       push @{$ret{TILT}}, 'gpgv reported a bad signature, but exited zero'
-         if 0 == $ret{exitcode};
-      } elsif ($1 eq 'ERR') {          # an ERRSIG line
-       $verdict_status++;
-       if (m/\G(\d+)\s(\d+)\s([[:xdigit:]]{2})\s([-:T[:digit:]Z+]+)\s(\d+)
-            /gcx) {
-       #  $1 -- pubkey algorithm        $2 -- digest algorithm
-       #  $3 -- timestamp               $4 -- result code
-         ftp_abort('gpgv returned an ISO8601 timestamp; implementation needed')
-           if $3 =~ m/T/;
-         $ret{sig_creation} = $3;
-       } else
-         { push @{$ret{TILT}}, 'gpgv ERRSIG line failed parsing' }
-
-       push @{$ret{TILT}}, 'gpgv reported an error, but exited zero'
-         if 0 == $ret{exitcode};
-      } else {                         # GOODSIG/EXPSIG/EXPKEYSIG/REVKEYSIG
-       $check_status++;
-      }
-    } elsif (m/\G(VALID)SIG\s([[:xdigit:]]+)\s(\d{4}-\d{2}-\d{2})\s
-              ([-:T[:digit:]Z+]+)\s([-:T[:digit:]Z+]+)\s(\d+)\s(\S+)\s
-              (\d+)\s(\d+)\s([[:xdigit:]]{2})\s([[:xdigit:]]+)
-             /gcx) {
-      $verdict_status++;
-      #  $1 -- valid tag                $2 -- key fingerprint
-      #  $3 -- signature date           $4 -- signature timestamp
-      #  $5 -- expiration timestamp     $6 -- signature version
-      #  $7 -- reserved                         $8 -- pubkey algorithm
-      #  $9 -- digest algorithm                $10 -- signature class
-      # $11 -- primary key fingerprint
-      $ret{key_fingerprint} = $2;
-      $ret{key_longid} = substr $2,-16;
-      ftp_abort('gpgv returned an ISO8601 timestamp; implementation needed')
-       if $4 =~ m/T/ || $5 =~ m/T/;
-      $ret{sig_creation} = $4;
-      # GPG reports 0 if the signature does not expire
-      $ret{sig_expiration} = $5 if $5 > 0;
-    }
-  }
-  close $status or ftp_abort('close in-memory file for gpgv status');
-
-  push @{$ret{TILT}}, 'gpgv reported more than one signature'
-    if $intro_status > 1;
-  push @{$ret{TILT}}, 'gpgv reported more than one signature check'
-    if $check_status > 1;
-  push @{$ret{TILT}}, 'gpgv reported more than one signature verdict'
-    if $verdict_status > 1;
-  push @{$ret{TILT}}, 'gpgv reported no signature verdict at all'
-    if $verdict_status < 1;
+  _analyze_gpgv_output(\%ret);
 
   return \%ret;
 }