diff options
| author | Slack Coder <slackcoder@server.ky> | 2024-12-10 14:06:06 -0500 | 
|---|---|---|
| committer | Slack Coder <slackcoder@server.ky> | 2025-01-21 15:36:10 -0500 | 
| commit | ba81bf24d8df5153830f06deb7c2b780fe5c292f (patch) | |
| tree | d8c0aefac933b7ed41e4b8faae75b9feaf0b9f1d /SBO-Lib/lib/SBO | |
| parent | 82b061208df57c0d1d46b06ffa15ad6846db883b (diff) | |
| download | sbotools2-ba81bf24d8df5153830f06deb7c2b780fe5c292f.tar.xz | |
GPG verification
Diffstat (limited to 'SBO-Lib/lib/SBO')
| -rw-r--r-- | SBO-Lib/lib/SBO/App/Snap.pm | 17 | ||||
| -rw-r--r-- | SBO-Lib/lib/SBO/Lib.pm | 7 | ||||
| -rw-r--r-- | SBO-Lib/lib/SBO/Lib/Cryptography.pm | 159 | ||||
| -rw-r--r-- | SBO-Lib/lib/SBO/Lib/Repo.pm | 147 | ||||
| -rw-r--r-- | SBO-Lib/lib/SBO/Lib/Util.pm | 5 | 
5 files changed, 304 insertions, 31 deletions
diff --git a/SBO-Lib/lib/SBO/App/Snap.pm b/SBO-Lib/lib/SBO/App/Snap.pm index 00aea38..d2b0f57 100644 --- a/SBO-Lib/lib/SBO/App/Snap.pm +++ b/SBO-Lib/lib/SBO/App/Snap.pm @@ -13,12 +13,12 @@ package SBO::App::Snap;  use 5.16.0;  use strict;  use warnings FATAL => 'all'; -use SBO::Lib qw/ fetch_tree update_tree %config show_version /; +use SBO::Lib qw/ fetch_tree import_gpg_key update_tree %config show_version /;  use Getopt::Long qw/ GetOptionsFromArray /;  use parent 'SBO::App'; -our $VERSION = '2.7.2'; +our $VERSION = '2.7.4';  sub _parse_opts {    my $class = shift; @@ -49,6 +49,7 @@ Options:  Commands:    fetch: initialize a local copy of the slackbuilds.org tree. +  import-key [path or url]: import GPG for verifying the slackbuilds.org tree. Defaults to the key shipped with sbotools2.    update: update an existing local copy of the slackbuilds.org tree.            (generally, you may prefer "sbocheck" over "$fname update") @@ -67,9 +68,17 @@ sub run {    $args[0] //= '';    if ($args[0] eq 'fetch') { -    fetch_tree() +    fetch_tree(); +  } elsif ($args[0] eq 'import-key') { +    my $key_path_or_url = "/usr/doc/sbotools2-$VERSION/slackbuilds-devel\@slackbuilds.org.asc"; +    if ($args[1]) { +      $key_path_or_url = $args[1]; +    } +    my $key_id = $config{'GPG_KEY'}; + +    import_gpg_key($key_path_or_url, $key_id);    } elsif ($args[0] eq 'update') { -    update_tree() +    update_tree();    } else {      $self->show_usage();      return 1; diff --git a/SBO-Lib/lib/SBO/Lib.pm b/SBO-Lib/lib/SBO/Lib.pm index ae67b23..edb418a 100644 --- a/SBO-Lib/lib/SBO/Lib.pm +++ b/SBO-Lib/lib/SBO/Lib.pm @@ -10,7 +10,7 @@ use strict;  use warnings FATAL => 'all';  package SBO::Lib; -our $VERSION = '2.7.2'; +our $VERSION = '2.7.4';  =pod @@ -33,6 +33,8 @@ exporting all of their exports.  =over +=item L<SBO::Lib::Cryptography> +  =item L<SBO::Lib::Util>  =item L<SBO::Lib::Info> @@ -53,6 +55,7 @@ exporting all of their exports.  =cut +use SBO::Lib::Cryptography qw/ :all /;  use SBO::Lib::Util qw/ :all /;  use SBO::Lib::Info qw/ :all /;  use SBO::Lib::Repo qw/ :all /; @@ -65,6 +68,7 @@ use SBO::Lib::Download qw/ :all /;  use Exporter 'import';  our @EXPORT_OK = ( +	@SBO::Lib::Cryptography::EXPORT_OK,  	@SBO::Lib::Util::EXPORT_OK,  	@SBO::Lib::Info::EXPORT_OK,  	@SBO::Lib::Repo::EXPORT_OK, @@ -77,6 +81,7 @@ our @EXPORT_OK = (  our %EXPORT_TAGS = (  	all => \@EXPORT_OK, +	cryptography => \@SBO::Lib::Cryptography::EXPORT_OK,  	util => \@SBO::Lib::Util::EXPORT_OK,  	info => \@SBO::Lib::Info::EXPORT_OK,  	repo => \@SBO::Lib::Repo::EXPORT_OK, diff --git a/SBO-Lib/lib/SBO/Lib/Cryptography.pm b/SBO-Lib/lib/SBO/Lib/Cryptography.pm new file mode 100644 index 0000000..4fca277 --- /dev/null +++ b/SBO-Lib/lib/SBO/Lib/Cryptography.pm @@ -0,0 +1,159 @@ +package SBO::Lib::Cryptography; + +use 5.016; +use strict; +use warnings; + +our $VERSION = '2.7.2'; + +use Cwd; +use File::Temp "tempdir"; +use IPC::Open3; + +use constant { +  BAD_SIGNATURE => 'bad signature', +  EXPIRED_KEY => 'expired key', +  VALID_SIGNATURE => 'good signature', +}; + +use Exporter 'import'; + +our @EXPORT_OK = qw{ +  has_valid_gpg_signature +  import_gpg_key +  verify_gpg_signed_file + +  BAD_SIGNATURE +  EXPIRED_KEY +  VALID_SIGNATURE +}; + +our %EXPORT_TAGS = ( +  all => \@EXPORT_OK, +); + +=pod + +=encoding UTF-8 + +=head2 + +  has_valid_gpg_signature(@output, $key_id); + +C<has_valid_gpg_siganture()> validates whether the captured gpg status output +contains a good signature for the given GPG key. + +=cut + +sub has_valid_gpg_signature { +  my $output = shift; +  my $key_id = shift; + +  # VALIDSIG contains the hex key ID, GOODSIG is required for certainty. More +  # information can be found in 'DETAILS' in the gnupg2 documentation folder. +  my $is_good_sig = 0; +  my $is_valid_sig = 0; + +  my $line; +  foreach $line (@$output) { +    if ($line =~ /^\[GNUPG\:] VALIDSIG $key_id/) { +      $is_valid_sig = 1; +    } elsif ($line =~ /^\[GNUPG\:] GOODSIG /) { +      $is_good_sig = 1; +    } + +    if ($is_good_sig && $is_valid_sig) { +      return 1; +    } +  } + +  return 0; +} + +=head2 import_gpg_key + +  import_gpg_key($key); + +C<import_gpg_key()> will import the key into the systems keychain.  An error will +be reported if the configured key fingerprint does not match the imported one. + +=cut + +sub import_gpg_key { +  script_error('import_gpg_key requires two arguments.') unless @_ == 2; + +  my $key = shift; +  my $key_id = shift; + +  my $key_source; +  if ($key =~ m!^http://! || $key =~ m!^https://!) { +    open3(undef, $key_source, ">&STDERR", "wget", $key, "-O", "-") || die("could not download key from $key"); +  } else { +    open($key_source, "<", $key) || die("could not read '$key': $!"); +  } + +  my $old = $ENV{'GNUPGHOME'}; + +  $ENV{'GNUPGHOME'} = tempdir(CLEANUP => 1); + +  my $gpg_cmd; +  open3($gpg_cmd, undef, undef, "gpg", "--batch", "--yes", "--import", "-") || die("could not import key: $!\n"); + +  while (my $line = <$key_source>) { +    print($gpg_cmd $line); +  } + +  close($gpg_cmd); +  close($key_source); + +  sleep(1); + +  if (system(">/dev/null gpg --list-keys $key_id")) { +    die("GPG key '$key_id' not found.  Confirm the correct key is configured or is being imported.\n"); +  } + +  my $gpg_export; +  open($gpg_export, "-|", "gpg", "--export", $key_id) || die("could not export key: $!\n"); + +  $ENV{'GNUPGHOME'} = $old; + +  my $gpg_import; +  open3($gpg_import, ">&STDOUT", ">&STDOUT", "gpg", "--batch", "--yes", "--import", "-") || die("could not import key: $!\n"); + +  while (my $line = <$gpg_export>) { +    print($gpg_import $line); +  } + +  close($gpg_export); +  close($gpg_import); + +  print("key imported\n"); +} + +=head2 verify_gpg_signed_file + +  verify_gpg_signed_file($file_path, $key_id); + +C<verify_gpg_signed_file()> verifies the C<file_path> is signed by C<key_id>.  + +=cut + +sub verify_gpg_signed_file { +  script_error('verify_gpg_signed_file requires two arguments.') unless @_ == 2; +   +  my $file_path = shift; +  my $key_id = shift; + +  my @output; +  open3(undef, my $std_out, undef, "gpg", "--status-fd=1", "--verify", $file_path) or die("dead"); +  while (my $line = <$std_out>) { +    push(@output, $line); +  } +  close($std_out); + +  if (! has_valid_gpg_signature(\@output, $key_id)) { +    return BAD_SIGNATURE; +  } + +  return VALID_SIGNATURE; +} diff --git a/SBO-Lib/lib/SBO/Lib/Repo.pm b/SBO-Lib/lib/SBO/Lib/Repo.pm index 35ed4c9..6c7babb 100644 --- a/SBO-Lib/lib/SBO/Lib/Repo.pm +++ b/SBO-Lib/lib/SBO/Lib/Repo.pm @@ -6,13 +6,17 @@ use warnings;  our $VERSION = '2.7.2'; -use SBO::Lib::Util qw/ %config prompt usage_error get_slack_version get_slack_version_url script_error open_fh open_read in _ERR_DOWNLOAD /; +use SBO::Lib::Util qw/ %config prompt usage_error get_slack_version get_slack_version_key get_slack_version_url script_error open_fh open_read in _ERR_DOWNLOAD /; +use SBO::Lib::Cryptography qw/ has_valid_gpg_signature verify_gpg_signed_file VALID_SIGNATURE /;  use Cwd;  use File::Copy;  use File::Find; +use File::Temp "tempdir";  use File::Path qw/ make_path remove_tree /; +use IPC::Open3;  use Sort::Versions; +use Symbol "gensym";  use Exporter 'import'; @@ -211,9 +215,26 @@ sub generate_slackbuilds_txt {    return 1;  } +sub latest_git_tag { +  my $version = shift; +  my $tag = ''; + +  open(my $std_out, "git tag |") or die("dead"); +  while (my $line = <$std_out>) { +    if ($line =~ /^$version-/) { +      $tag = $line; +    } +  } +  close($std_out); + +  chomp($tag); + +  return $tag; +} +  =head2 git_sbo_tree -  my $bool = git_sbo_tree($url); +  my $bool = git_sbo_tree($url, $key_id);  C<git_sbo_tree()> will C<git clone> the repository specified by C<$url> to the  C<$repo_path> if the C<$url> repository isn't already there. If it is, it will @@ -225,28 +246,62 @@ true value.  =cut  sub git_sbo_tree { -  script_error('git_sbo_tree requires an argument.') unless @_ == 1; +  script_error('git_sbo_tree requires two arguments.') unless @_ == 2;    my $url = shift; +  my $key_id = shift; +    my $cwd = getcwd(); -  my $res; -  if (-d "$repo_path/.git" and check_git_remote($repo_path, $url)) { -    _race::cond '$repo_path can be deleted after -d check'; -    chdir $repo_path or return 0; -    $res = eval { -      die unless system(qw! git fetch !) == 0; # if system() doesn't return 0, there was an error -      _race::cond 'git repo could be changed or deleted here'; -      die unless system(qw! git reset --hard origin !) == 0; -      unlink "$repo_path/SLACKBUILDS.TXT"; -      1; -    }; -  } else { + +  if ((! -d "$repo_path/.git") || ! check_git_remote($repo_path, $url)) {      chdir $config{SBO_HOME} or return 0; +      remove_tree($repo_path) if -d $repo_path; -    $res = system(qw/ git clone --no-local /, $url, $repo_path) == 0; +    if (system(qw/ git clone --no-local /, $url, $repo_path)) { +      return 0; +    } +  } + +  _race::cond '$repo_path can be deleted after -d check'; +  chdir($repo_path) or return 0; + +  return 0 unless system("git fetch") == 0; + +  unlink "$repo_path/SLACKBUILDS.TXT"; + +  my $git_ref = 'origin'; +  my $verify_cmd = 'verify-commit'; + +  my $tag = latest_git_tag(get_slack_version()); +  if ($tag ne '') { +    $git_ref = $tag; +    $verify_cmd = 'verify-tag'; +  } + +  if ($key_id) { +    my @output; + +    print("Verifying $git_ref...\n"); +    open3(undef, undef, my $std_err = gensym, "git", $verify_cmd, "--raw", "$git_ref"); +    while (my $line = <$std_err>) { +      push(@output, $line); +    } +    close($std_err); + +    if (! has_valid_gpg_signature(\@output, $key_id)) { +      print(STDERR "Repository GPG verification failed.\n"); + +      chdir $cwd; +      return 0; +    }    } + +  _race::cond 'git repo could be changed or deleted here'; +  return 0 unless system('git', 'reset', '--hard', $git_ref) == 0; +    _race::cond '$cwd could be deleted here'; -  return 1 if chdir $cwd and $res; -  return 0; +  return 0 unless chdir $cwd; + +  return 1;  }  =head2 migrate_repo @@ -292,14 +347,26 @@ sub pull_sbo_tree {    } else {      unlink($slackbuilds_txt);    } + +  my $key_id = ''; +  if ($config{GPG_KEY} ne 'FALSE') { +    $key_id = $config{GPG_KEY}; +  }; +    my $res = 0;    if ($url =~ m!^rsync://!) { -    $res = rsync_sbo_tree($url); +    $res = rsync_sbo_tree($url, $key_id);    } else { -    $res = git_sbo_tree($url); +    $res = git_sbo_tree($url, $key_id);    } -  if ($res == 0) { warn "Could not sync from $url.\n"; exit _ERR_DOWNLOAD; } +  if ($res == 0) { +    warn "Could not sync from $url.\n"; +    if ($url eq 'https://github.com/Ponce/slackbuilds.git' && $key_id ne '') { +      warn "This URL is known not to use GPG verification.  You likely want to disable with 'sboconfig --gpg-key FALSE'." +    } +    exit _ERR_DOWNLOAD; +  }    my $wanted = sub { chown 0, 0, $File::Find::name; };    find($wanted, $repo_path) if -d $repo_path; @@ -310,7 +377,7 @@ sub pull_sbo_tree {  =head2 rsync_sbo_tree -  my $bool = rsync_sbo_tree($url); +  my $bool = rsync_sbo_tree($url, $key_id);  C<rsync_sbo_tree()> syncs the SlackBuilds.org repository to C<$repo_path> from  the C<$url> provided. @@ -319,14 +386,46 @@ the C<$url> provided.  # rsync the sbo tree from slackbuilds.org to $repo_path  sub rsync_sbo_tree { -  script_error('rsync_sbo_tree requires an argument.') unless @_ == 1; +  script_error('rsync_sbo_tree requires two arguments.') unless @_ == 2; +    my $url = shift;    $url .= '/' unless $url =~ m!/$!; # make sure $url ends with / +  my $key_id = shift; +    my @info;    # only slackware versions above 14.1 have an rsync that supports --info=progress2    if (versioncmp(get_slack_version(), '14.1') == 1) { @info = ('--info=progress2'); } +    my @args = ('rsync', @info, '-a', '--delete', $url); -  return system(@args, $repo_path) == 0; +  return 0 unless system(@args, $repo_path) == 0; + +  my $cwd = getcwd(); +  chdir($repo_path); + +  if ($key_id) { +    if (versioncmp(get_slack_version(), '14.1') == -1) { +      print("GPG verification is not present for 14.0 and earlier.  You should consider disabling GPG verification.") +    } + +    print("Verifying CHECKSUMS.md5...\n"); +    my $res = verify_gpg_signed_file('CHECKSUMS.md5.asc', $key_id); +    if ($res ne VALID_SIGNATURE) { +      print(STDERR "Respository CHECKSUMS.md5 GPG verification failed.\n"); + +      chdir($cwd); +      return 0; +    } +  } + +  if ( -e "CHECKSUMS.md5" ) { +    print("Verifying file integrity using CHECKSUMS.md5...\n"); +    if (system('tail +13 CHECKSUMS.md5 | md5sum -c --quiet -')) { +      chdir($cwd); +      return 0; +    } +  } + +  return chdir($cwd);  }  =head2 slackbuilds_or_fetch diff --git a/SBO-Lib/lib/SBO/Lib/Util.pm b/SBO-Lib/lib/SBO/Lib/Util.pm index d6327c1..3c611b8 100644 --- a/SBO-Lib/lib/SBO/Lib/Util.pm +++ b/SBO-Lib/lib/SBO/Lib/Util.pm @@ -40,6 +40,7 @@ our @EXPORT_OK = (      get_kernel_version      get_sbo_from_loc      get_slack_version +    get_slack_version_key      get_slack_version_url      idx      in @@ -97,7 +98,7 @@ the values will change according to the configuration, and C<SBO_HOME> will by  default get changed to C</usr/sbo>.  The supported keys are: C<NOCLEAN>, C<DISTCLEAN>, C<JOBS>, C<PKG_DIR>, -C<SBO_HOME>, C<LOCAL_OVERRIDES>, C<SLACKWARE_VERSION>, C<REPO>. +C<SBO_HOME>, C<LOCAL_OVERRIDES>, C<SLACKWARE_VERSION>, C<REPO>, C<GPG_KEY>.  =cut @@ -113,6 +114,7 @@ our %config = (    LOCAL_OVERRIDES => 'FALSE',    SLACKWARE_VERSION => 'FALSE',    REPO => 'FALSE', +  GPG_KEY => 'D3076BC3E783EE747F09B8B70368EF579C7BA3B6',  );  read_config(); @@ -242,7 +244,6 @@ sub get_slack_version_url {    return $supported{get_slack_version()};  } -  =head2 idx    my $idx = idx($needle, @haystack);  | 
