aboutsummaryrefslogtreecommitdiff
path: root/SBO-Lib/lib
diff options
context:
space:
mode:
authorSlack Coder <slackcoder@server.ky>2024-12-10 14:06:06 -0500
committerSlack Coder <slackcoder@server.ky>2025-01-21 15:36:10 -0500
commitba81bf24d8df5153830f06deb7c2b780fe5c292f (patch)
treed8c0aefac933b7ed41e4b8faae75b9feaf0b9f1d /SBO-Lib/lib
parent82b061208df57c0d1d46b06ffa15ad6846db883b (diff)
downloadsbotools2-ba81bf24d8df5153830f06deb7c2b780fe5c292f.tar.xz
GPG verification
Diffstat (limited to 'SBO-Lib/lib')
-rw-r--r--SBO-Lib/lib/SBO/App/Snap.pm17
-rw-r--r--SBO-Lib/lib/SBO/Lib.pm7
-rw-r--r--SBO-Lib/lib/SBO/Lib/Cryptography.pm159
-rw-r--r--SBO-Lib/lib/SBO/Lib/Repo.pm147
-rw-r--r--SBO-Lib/lib/SBO/Lib/Util.pm5
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);