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 | |
parent | 82b061208df57c0d1d46b06ffa15ad6846db883b (diff) | |
download | sbotools2-ba81bf24d8df5153830f06deb7c2b780fe5c292f.tar.xz |
GPG verification
Diffstat (limited to 'SBO-Lib/lib')
-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); |