This is an automated email from the git hooks/post-receive script. guillem pushed a commit to branch main in repository dpkg.
View the commit online: https://git.dpkg.org/cgit/dpkg/dpkg.git/commit/?id=6afb4b99eefeebd3de2868b590ce42def0906446 commit 6afb4b99eefeebd3de2868b590ce42def0906446 Author: Guillem Jover <[email protected]> AuthorDate: Fri Jul 29 23:25:39 2022 +0200 Dpkg::OpenPGP: Add multi-backend loading support This adds the generic infrastructure to be able to load multiple OpenPGP backends, depending on the requested CLI, API, or specific needed features when in auto mode. --- scripts/Dpkg/Control/HashCore.pm | 2 +- scripts/Dpkg/OpenPGP.pm | 63 +++++++++++++++++++- scripts/Dpkg/Source/Package.pm | 2 +- scripts/dpkg-buildpackage.pl | 3 + scripts/t/Dpkg_OpenPGP.t | 121 ++++++++++++++++++++++----------------- 5 files changed, 136 insertions(+), 55 deletions(-) diff --git a/scripts/Dpkg/Control/HashCore.pm b/scripts/Dpkg/Control/HashCore.pm index b45bc512e..e31ef299e 100644 --- a/scripts/Dpkg/Control/HashCore.pm +++ b/scripts/Dpkg/Control/HashCore.pm @@ -274,7 +274,7 @@ sub parse { $self->parse_error($desc, g_('unfinished OpenPGP signature')); } # This does not mean the signature is correct, that needs to - # be verified by gnupg. + # be verified by an OpenPGP backend. $$self->{is_pgp_signed} = 1; } last; # Finished parsing one block diff --git a/scripts/Dpkg/OpenPGP.pm b/scripts/Dpkg/OpenPGP.pm index 06286d61d..f45c1ff7d 100644 --- a/scripts/Dpkg/OpenPGP.pm +++ b/scripts/Dpkg/OpenPGP.pm @@ -18,14 +18,22 @@ package Dpkg::OpenPGP; use strict; use warnings; +use List::Util qw(none); + use Dpkg::Gettext; use Dpkg::ErrorHandling; use Dpkg::IPC; use Dpkg::Path qw(find_command); -use Dpkg::OpenPGP::Backend::GnuPG; our $VERSION = '0.01'; +my @BACKENDS = qw( + gpg +); +my %BACKEND = ( + gpg => 'GnuPG', +); + sub new { my ($this, %opts) = @_; my $class = ref($this) || $this; @@ -33,16 +41,67 @@ sub new { my $self = {}; bless $self, $class; + my $backend = $opts{backend} // 'auto'; my %backend_opts = ( cmdv => $opts{cmdv} // 'auto', cmd => $opts{cmd} // 'auto', ); - $self->{backend} = Dpkg::OpenPGP::Backend::GnuPG->new(%backend_opts); + if ($backend eq 'auto') { + # Defaults for stateless full API auto-detection. + $opts{needs}{api} //= 'full'; + $opts{needs}{keystore} //= 0; + + if (none { $opts{needs}{api} eq $_ } qw(full verify)) { + error(g_('unknown OpenPGP api requested %s'), $opts{needs}{api}); + } + + $self->{backend} = $self->_auto_backend($opts{needs}, %backend_opts); + } elsif (exists $BACKEND{$backend}) { + $self->{backend} = $self->_load_backend($BACKEND{$backend}, %backend_opts); + if (! $self->{backend}) { + error(g_('cannot load OpenPGP backend %s'), $backend); + } + } else { + error(g_('unknown OpenPGP backend %s'), $backend); + } return $self; } +sub _load_backend { + my ($self, $backend, %opts) = @_; + + my $module = "Dpkg::OpenPGP::Backend::$backend"; + eval qq{ + pop \@INC if \$INC[-1] eq '.'; + require $module; + }; + return if $@; + + return $module->new(%opts); +} + +sub _auto_backend { + my ($self, $needs, %opts) = @_; + + foreach my $backend (@BACKENDS) { + my $module = $self->_load_backend($BACKEND{$backend}, %opts); + + if ($needs->{api} eq 'verify') { + next if ! $module->has_verify_cmd(); + } else { + next if ! $module->has_backend_cmd(); + } + next if $needs->{keystore} && ! $module->has_keystore(); + + return $module; + } + + # Otherwise load a dummy backend. + return Dpkg::OpenPGP::Backend->new(); +} + sub can_use_secrets { my ($self, $key) = @_; diff --git a/scripts/Dpkg/Source/Package.pm b/scripts/Dpkg/Source/Package.pm index 6d0e86392..1956ad06e 100644 --- a/scripts/Dpkg/Source/Package.pm +++ b/scripts/Dpkg/Source/Package.pm @@ -212,7 +212,7 @@ sub new { fields => Dpkg::Control->new(type => CTRL_PKG_SRC), format => Dpkg::Source::Format->new(), options => {}, - checksums => Dpkg::Checksums->new(), + checksums => Dpkg::Checksums->new(needs => { api => 'verify' }), openpgp => Dpkg::OpenPGP->new(), }; bless $self, $class; diff --git a/scripts/dpkg-buildpackage.pl b/scripts/dpkg-buildpackage.pl index 2c204b8ef..440edf0dd 100755 --- a/scripts/dpkg-buildpackage.pl +++ b/scripts/dpkg-buildpackage.pl @@ -545,6 +545,9 @@ signkey_validate(); my $openpgp = Dpkg::OpenPGP->new( cmd => $signcommand // 'auto', + needs => { + keystore => $signkey->needs_keystore(), + }, ); if (not $openpgp->can_use_secrets($signkey)) { diff --git a/scripts/t/Dpkg_OpenPGP.t b/scripts/t/Dpkg_OpenPGP.t index 4dea39f6f..849014afc 100644 --- a/scripts/t/Dpkg_OpenPGP.t +++ b/scripts/t/Dpkg_OpenPGP.t @@ -22,20 +22,30 @@ use Test::Dpkg qw(:paths :needs); use File::Compare; use Dpkg::ErrorHandling; +use Dpkg::Path qw(find_command); use Dpkg::OpenPGP::KeyHandle; -test_needs_command('gpg'); +my @backend_cmds = qw( + gpg +); +my %backend_cmd = ( + auto => 'auto', + gpg => 'gpg', +); +my @cmds = grep { find_command($_) } @backend_cmds; +if (@cmds == 0) { + plan skip_all => "requires at least one backend command: @backend_cmds"; +} -plan tests => 17; +unshift @cmds, 'auto'; + +plan tests => 2 + 15 * scalar @cmds; use_ok('Dpkg::OpenPGP'); use_ok('Dpkg::OpenPGP::ErrorCodes'); report_options(quiet_warnings => 1); -my $datadir = test_get_data_path(); -my $tempdir = test_get_temp_path(); - sub test_diff { my ($exp_file, $gen_file, $desc) = @_; @@ -47,51 +57,60 @@ sub test_diff ok($res == 0, "$desc ($exp_file vs $gen_file)"); } -my $openpgp = Dpkg::OpenPGP->new(); - -ok($openpgp->dearmor('PUBLIC KEY BLOCK', "$datadir/dpkg-test-pub.asc", "$tempdir/dpkg-test-pub.pgp") == OPENPGP_OK(), - 'dearmoring OpenPGP ASCII Armored certificate'); -ok($openpgp->armor('PUBLIC KEY BLOCK', "$tempdir/dpkg-test-pub.pgp", "$tempdir/dpkg-test-pub.asc") == OPENPGP_OK(), - 'armoring OpenPGP binary certificate'); -test_diff("$datadir/dpkg-test-pub.asc", "$tempdir/dpkg-test-pub.asc", - 'OpenPGP certificate dearmor/armor round-trip correctly'); - -ok($openpgp->armor('SIGNATURE', "$datadir/sign-file.sig", "$tempdir/sign-file.asc") == OPENPGP_OK(), - 'armoring OpenPGP binary signature succeeded'); -ok(compare("$datadir/sign-file.sig", "$tempdir/sign-file.asc") != 0, - 'armoring OpenPGP ASCII Armor changed the file'); -ok($openpgp->armor('SIGNATURE', "$datadir/sign-file.asc", "$tempdir/sign-file-rearmor.asc") == OPENPGP_OK(), - 'armoring OpenPGP armored signature succeeded'); -test_diff("$datadir/sign-file.asc", "$tempdir/sign-file-rearmor.asc", - 'rearmoring OpenPGP ASCII Armor changed the file'); - -ok($openpgp->dearmor('SIGNATURE', "$tempdir/sign-file.asc", "$tempdir/sign-file.sig") == OPENPGP_OK(), - 'dearmoring OpenPGP armored signature succeeded'); -test_diff("$datadir/sign-file.sig", "$tempdir/sign-file.sig", - 'dearmored OpenPGP ASCII Armor signature matches'); - -my $cert = "$datadir/dpkg-test-pub.asc"; - -ok($openpgp->inline_verify("$datadir/sign-file-inline.asc", undef, $cert) == OPENPGP_OK(), - 'verify OpenPGP ASCII Armor inline signature'); -ok($openpgp->inline_verify("$datadir/sign-file-inline.sig", undef, $cert) == OPENPGP_OK(), - 'verify OpenPGP binary inline signature'); - -ok($openpgp->verify("$datadir/sign-file", "$datadir/sign-file.asc", $cert) == OPENPGP_OK(), - 'verify OpenPGP ASCII Armor detached signature'); -ok($openpgp->verify("$datadir/sign-file", "$datadir/sign-file.sig", $cert) == OPENPGP_OK(), - 'verify OpenPGP binary detached signature'); - -my $key = Dpkg::OpenPGP::KeyHandle->new( - type => 'keyfile', - handle => "$datadir/dpkg-test-sec.asc", -); - -ok($openpgp->inline_sign("$datadir/sign-file", "$tempdir/sign-file-inline.asc", $key) == OPENPGP_OK(), - 'inline OpenPGP sign'); -ok($openpgp->inline_verify("$tempdir/sign-file-inline.asc", undef, $cert) == OPENPGP_OK(), - 'verify generated inline OpenPGP signature'); - -# TODO: Add actual test cases. +foreach my $cmd (@cmds) { + my $datadir = test_get_data_path(); + my $tempdir = test_get_temp_path(); + + my $backend = $backend_cmd{$cmd}; + my $openpgp = Dpkg::OpenPGP->new( + backend => $backend, + cmd => $cmd, + ); + + ok($openpgp->dearmor('PUBLIC KEY BLOCK', "$datadir/dpkg-test-pub.asc", "$tempdir/dpkg-test-pub.pgp") == OPENPGP_OK(), + "($backend:$cmd) dearmoring OpenPGP ASCII Armored certificate"); + ok($openpgp->armor('PUBLIC KEY BLOCK', "$tempdir/dpkg-test-pub.pgp", "$tempdir/dpkg-test-pub.asc") == OPENPGP_OK(), + "($backend:$cmd) armoring OpenPGP binary certificate"); + test_diff("$datadir/dpkg-test-pub.asc", "$tempdir/dpkg-test-pub.asc", + "($backend:$cmd) OpenPGP certificate dearmor/armor round-trip correctly"); + + ok($openpgp->armor('SIGNATURE', "$datadir/sign-file.sig", "$tempdir/sign-file.asc") == OPENPGP_OK(), + "($backend:$cmd) armoring OpenPGP binary signature succeeded"); + ok(compare("$datadir/sign-file.sig", "$tempdir/sign-file.asc") != 0, + "($backend:$cmd) armoring OpenPGP ASCII Armor changed the file"); + ok($openpgp->armor('SIGNATURE', "$datadir/sign-file.asc", "$tempdir/sign-file-rearmor.asc") == OPENPGP_OK(), + "($backend:$cmd) armoring OpenPGP armored signature succeeded"); + test_diff("$datadir/sign-file.asc", "$tempdir/sign-file-rearmor.asc", + "($backend:$cmd) rearmoring OpenPGP ASCII Armor changed the file"); + + ok($openpgp->dearmor('SIGNATURE', "$tempdir/sign-file.asc", "$tempdir/sign-file.sig") == OPENPGP_OK(), + "($backend:$cmd) dearmoring OpenPGP armored signature succeeded"); + test_diff("$datadir/sign-file.sig", "$tempdir/sign-file.sig", + "($backend:$cmd) dearmored OpenPGP ASCII Armor signature matches"); + + my $cert = "$datadir/dpkg-test-pub.asc"; + + ok($openpgp->inline_verify("$datadir/sign-file-inline.asc", undef, $cert) == OPENPGP_OK(), + "($backend:$cmd) verify OpenPGP ASCII Armor inline signature"); + ok($openpgp->inline_verify("$datadir/sign-file-inline.sig", undef, $cert) == OPENPGP_OK(), + "($backend:$cmd) verify OpenPGP binary inline signature"); + + ok($openpgp->verify("$datadir/sign-file", "$datadir/sign-file.asc", $cert) == OPENPGP_OK(), + "($backend:$cmd) verify OpenPGP ASCII Armor detached signature"); + ok($openpgp->verify("$datadir/sign-file", "$datadir/sign-file.sig", $cert) == OPENPGP_OK(), + "($backend:$cmd) verify OpenPGP binary detached signature"); + + my $key = Dpkg::OpenPGP::KeyHandle->new( + type => 'keyfile', + handle => "$datadir/dpkg-test-sec.asc", + ); + + ok($openpgp->inline_sign("$datadir/sign-file", "$tempdir/sign-file-inline.asc", $key) == OPENPGP_OK(), + "($backend:$cmd) inline OpenPGP sign"); + ok($openpgp->inline_verify("$tempdir/sign-file-inline.asc", undef, $cert) == OPENPGP_OK(), + "($backend:$cmd) verify generated inline OpenPGP signature"); + + # TODO: Add more test cases. +} 1; -- Dpkg.Org's dpkg

