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 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 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 verifies the C is signed by C. =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; }