[Rt-commit] rt branch, 5.0/support-gpg2, repushed
Aaron Trevena
ast at bestpractical.com
Thu May 14 13:50:54 EDT 2020
The branch 5.0/support-gpg2 was deleted and repushed:
was 4cf7e6562ca7d7b553943f2a176ee99fc4664059
now 040a81b0a1d6fb3252140cb1d8adbffc0f50273b
1: 6c1926d300 = 1: 6c1926d300 Add new AvailableKeys method to RT::User
2: 261f0df6aa = 2: 261f0df6aa Handle new GPG 2.2.x status keywords
3: 6c6d5c753d < --: ------- Updated web crypt gnupg test to work with new GPG
4: ca7dec9a15 < --: ------- wip
5: bde623ac53 ! 3: 1fe97cdda8 Updated web crypt gnupg test to work with new GPG
@@ -1,6 +1,6 @@
Author: Aaron Trevena <ast at bestpractical.com>
- wip t/mail/crypt-gnupg.t
+ Updated web crypt gnupg test to work with new GPG
diff --git a/t/mail/crypt-gnupg.t b/t/mail/crypt-gnupg.t
--- a/t/mail/crypt-gnupg.t
@@ -50,29 +50,17 @@
ok( $entity->is_multipart, 'signed message is multipart' );
is( $entity->parts, 2, 'two parts' );
@@
- Data => ['test'],
- );
- my %res;
--# Failed test at t/mail/crypt-gnupg.t line 73.
--# found warning: gpg: no default secret key: bad passphrase
- warning_like {
- %res = RT::Crypt->SignEncrypt(
- Entity => $entity,
-@@
+ Encrypt => 0,
Passphrase => ''
);
- } qr/(no default secret key|can't query passphrase in batch mode)/;
+- } qr/can't query passphrase in batch mode/;
++ } qr/(no default secret key|can't query passphrase in batch mode)/;
+ use Data::Dumper;
+ warn Dumper({res => \%res });
+
ok( $res{'exit_code'}, "couldn't sign without passphrase");
ok( $res{'error'} || $res{'logger'}, "error is here" );
-- use Data::Dumper;
-- warn Dumper( { error => $res{'error'},
-- logger => $res{'logger'} );
- my @status = RT::Crypt->ParseStatus(
- Protocol => $res{'Protocol'}, Status => $res{'status'}
@@
Entity => $entity,
Sign => 0,
@@ -113,3 +101,68 @@
ok($entity, 'get an encrypted and signed part');
+diff --git a/t/web/crypt-gnupg.t b/t/web/crypt-gnupg.t
+--- a/t/web/crypt-gnupg.t
++++ b/t/web/crypt-gnupg.t
+@@
+ $nokey->PrincipalObj->GrantRight(Right => 'CreateTicket');
+ $nokey->PrincipalObj->GrantRight(Right => 'OwnTicket');
+
++# qr/.*nokey\@example.com' via WKD: No data/,
+ my $tick = RT::Ticket->new( RT->SystemUser );
+-warning_like {
++warnings_exist {
+ $tick->Create(Subject => 'owner lacks pubkey', Queue => 'general',
+ Owner => $nokey);
+ } [
+- qr/nokey\@example.com: skipped: public key not found/,
+ qr/Recipient 'nokey\@example.com' is unusable/,
+ ];
+ ok(my $id = $tick->id, 'created ticket for owner-without-pubkey');
+@@
+
+ hello
+ MAIL
+-
++
+ my $status;
+-warning_like {
++warnings_exist {
+ ($status, $id) = RT::Test->send_via_mailgate($mail);
+ } [
+- qr/nokey\@example.com: skipped: public key not found/,
++ qr/nokey\@example.com.\s(skipped: public key not found|via WKD: No data)/,
+ qr/Recipient 'nokey\@example.com' is unusable/,
+ ];
+
+@@
+ "Correct subject"
+ );
+
+-# test key selection
++# test key selection, GPG2.2+ can pick different signatures for keys based on updated rules/behaviour
++# Note: need to check that upgrading with old preferred keys in db will still work correctly with GPG2
+ my $key1 = "EC1E81E7DC3DB42788FB0E4E9FA662C06DE22FC2";
+ my $key2 = "75E156271DCCF02DDD4A7A8CDF651FA0632C4F50";
++my $key_sig1 = '299728D87681E34B744E31B046CA26A25AB388D9';
++my $key_sig2 = '4A203DDC79EBD0CC9D48BA94568810E072208BA5';
++
++# get key1/2 from gpg and update if updated fingerprint
++my @keys_from_gpg = $user->AvailableKeys;
++$key1 = $key_sig1 if (shift(@keys_from_gpg) eq $key_sig1 );
++$key2 = $key_sig2 if (shift(@keys_from_gpg) eq $key_sig2 );
+
+ ok($user = RT::User->new(RT->SystemUser));
+ ok($user->Load('root'), "Loaded user 'root'");
+@@
+ like($content, qr/KR-nokey \(no pubkey!\)-K/,
+ "KeyRequestors DOES issue no-pubkey warning for nokey\@example.com");
+
+-$m->next_warning_like(qr/public key not found/);
+-$m->next_warning_like(qr/public key not found/);
++$m->next_warning_like(qr/(public key not found|No public key)/);
++$m->next_warning_like(qr/(public key not found|No public key)/);
+ $m->no_leftover_warnings_ok;
+
+ done_testing;
+
6: 3adc7df363 ! 4: 689e96f41a Allow empty default key for GPG 2.2 in RT::Crypt::GnuPG
@@ -1,6 +1,6 @@
Author: Aaron Trevena <ast at bestpractical.com>
- wip RT::Crypt::GnuPG
+ Allow empty default key for GPG 2.2 in RT::Crypt::GnuPG
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
--- a/lib/RT/Crypt/GnuPG.pm
7: 286e3959c2 = 5: 00ae5d72eb Add GnuPG 2.1+ specific test homedir and supporting test files
8: 440a6b6b1f = 6: 0dac4c5b85 Allow GnuPG 2.1 warning, keybox instead of keyring
9: 2dae5e9891 = 7: 0824ebcf4a add extra keywords for gnupg 2.2.x
10: 6d9f54c192 = 8: 8a945701f7 Parse GnuPG subkeys for 2.x
11: 5866c582c8 = 9: c361a6abe7 Added warnings_exist method to RT::Test::Web
12: a2167dbd16 ! 10: 63d649e662 Updated RT::Test::GnuPG to prepare homedir and agent for GPG2 testing
@@ -1,6 +1,38 @@
Author: Aaron Trevena <ast at bestpractical.com>
Updated RT::Test::GnuPG to prepare homedir and agent for GPG2 testing
+
+ Added helpers and setup for GPG 2 to RT::Test::GnuPG
+
+diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
+--- a/lib/RT/Test.pm
++++ b/lib/RT/Test.pm
+@@
+ RT::Test->file_content( [ $path, $key ] ) );
+ }
+
++
++sub import_gnupg2_key {
++ my $self = shift;
++ my $key = shift;
++ my $type = shift || 'secret';
++
++ $key =~ s/\@/-at-/g;
++ $key .= ".$type.key";
++
++ my $path = find_relocatable_path( 'data', 'gnupg2', 'keys' );
++
++ die "can't find the dir where gnupg keys are stored"
++ unless $path;
++
++ return RT::Crypt::GnuPG->ImportKey(
++ RT::Test->file_content( [ $path, $key ] ) );
++}
++
++
+ sub lsign_gnupg_key {
+ my $self = shift;
+ my $key = shift;
diff --git a/lib/RT/Test/GnuPG.pm b/lib/RT/Test/GnuPG.pm
--- a/lib/RT/Test/GnuPG.pm
@@ -17,25 +49,21 @@
our @EXPORT =
- qw(create_a_ticket update_ticket cleanup_headers set_queue_crypt_options
+- check_text_emails send_email_and_check_transaction
+ qw(create_a_ticket update_ticket cleanup_headers set_queue_crypt_options
- check_text_emails send_email_and_check_transaction
++ check_text_emails send_email_and_check_trangnsaction
create_and_test_outgoing_emails
++ copy_test_keys_to_homedir copy_test_keyring_to_homedir
++ get_test_gnupg_interface get_test_data_dir
++ $homedir $gnupg_version $using_legacy_gnupg
);
-+use vars qw($homedir $gnupg_version $using_legacy_gnupg);
+no warnings qw(redefine once);
+
+BEGIN {
-+ if (-f "test/gnupghome") {
-+ my $record = IO::File->new( "< t/data/gnupghome" );
-+ $homedir = <$record>;
-+ $record->close();
-+ } else {
-+ $homedir = tempdir( DIR => '/tmp');
-+ my $record = IO::File->new( "> t/data/gnupghome" );
-+ $record->write($homedir);
-+ $record->close();
-+ }
++ use vars qw($homedir $gnupg_version $using_legacy_gnupg);
++ my $tempdir_template = 'test_gnupg_XXXXXXXXX';
++ $homedir = tempdir( $tempdir_template, DIR => '/tmp', CLEANUP => 1);
+
+ $ENV{'GNUPGHOME'} = $homedir;
+
@@ -66,11 +94,6 @@
+ );
+
+ *RT::Crypt::GnuPG::_PrepareGnuPGOptions = sub {
-+
-+ # you are here - need to force no-default-key option, maybe add as a flag to GPG::Interface
-+ # also need to copy / specify keychain, etc from new 2.1 test dir maybe - provide helper to handle that here
-+ # also need to update references to 2.1 to 2.2
-+ # Added --keyring and --no-default-keyring options to GnuPG::Options
+ my %opt = @_;
+ $opt{homedir} = $homedir;
+ my %res = map { lc $_ => $opt{ $_ } } grep $supported_opt{ lc $_ }, keys %opt;
@@ -84,7 +107,8 @@
+ };
+
+ make_path($homedir, { mode => 0700 });
-+ copy('t/data//gpg.conf', $homedir . '/gpg.conf');
++ my $data_path = RT::Test::get_abs_relocatable_dir( File::Spec->updir(), 'data');
++ copy('$data_path/gpg.conf', $homedir . '/gpg.conf');
+
+ my $gnupg = GnuPG::Interface->new;
+ $gnupg->options->hash_init(
@@ -96,14 +120,18 @@
+
+ if ($gnupg->cmp_version($gnupg_version, '2.2') >= 0 ) {
+ $using_legacy_gnupg = 0;
++
+ my $agentconf = IO::File->new( "> " . $homedir . "/gpg-agent.conf" );
+ # Classic gpg can't use loopback pinentry programs like fake-pinentry.pl.
+
++ # default to empty passphrase pinentry
++ # passphrase in "pinentry-program $data_path/gnupg2/bin/fake-pinentry.pl\n"
+ $agentconf->write(
+ "allow-preset-passphrase\n".
+ "allow-loopback-pinentry\n".
-+ "pinentry-program " . getcwd() . "/test/fake-pinentry.pl\n"
++ "pinentry-program $data_path/gnupg2/bin/empty-pinentry.pl\n"
+ );
++
+ $agentconf->close();
+
+ my $error = system("gpg-connect-agent", "--homedir", "$homedir", '/bye');
@@ -120,7 +148,14 @@
+ if ($error) {
+ warn "gpg-agent returned error : $error";
+ }
-+
++ }
++}
++
++
++END {
++ unless ($using_legacy_gnupg) {
++ system('gpgconf', '--homedir', $homedir,'--quiet', '--kill', 'gpg-agent');
++ delete $ENV{'GNUPGHOME'};
+ }
+}
+
@@ -137,4 +172,82 @@
$class->set_rights(
Principal => 'Everyone',
-
+@@
+ }
+ }
+ }
++
++sub copy_test_keyring_to_homedir {
++ my (%args) = @_;
++ my $srcdir;
++ if ($using_legacy_gnupg || $args{use_legacy_keys}) {
++ $srcdir =
++ RT::Test::get_abs_relocatable_dir( File::Spec->updir(),
++ qw/data gnupg keyrings/ );
++ }
++ else {
++ $srcdir =
++ RT::Test::get_abs_relocatable_dir( File::Spec->updir(),
++ qw/data gnupg2 keyrings/ );
++ }
++ opendir(my $DIR, $srcdir) || die "can't opendir $srcdir: $!";
++ my @files = readdir($DIR);
++ foreach my $file (@files) {
++ if(-f "$srcdir/$file" ) {
++ copy "$srcdir/$file", "$homedir/$file";
++ }
++ }
++ closedir($DIR);
++}
++
++sub copy_test_keys_to_homedir {
++ my (%args) = @_;
++ my $srcdir;
++ if ($using_legacy_gnupg || $args{use_legacy_keys}) {
++ $srcdir =
++ RT::Test::get_abs_relocatable_dir( File::Spec->updir(),
++ qw/data gnupg keys/ );
++ }
++ else {
++ $srcdir =
++ RT::Test::get_abs_relocatable_dir( File::Spec->updir(),
++ qw/data gnupg2 keys/ );
++ }
++
++ opendir(my $DIR, $srcdir) || die "can't opendir $srcdir: $!";
++ my @files = readdir($DIR);
++ foreach my $file (@files) {
++ if(-f "$srcdir/$file" ) {
++ copy "$srcdir/$file", "$homedir/$file";
++ }
++ }
++ closedir($DIR);
++}
++
++sub get_test_data_dir {
++ my (%args) = @_;
++ my $test_data_dir;
++ if ($using_legacy_gnupg || $args{use_legacy_keys}) {
++ $test_data_dir = RT::Test::get_abs_relocatable_dir( File::Spec->updir(),
++ qw(data gnupg keyrings) );
++
++ }
++ else {
++ $test_data_dir = RT::Test::get_abs_relocatable_dir( File::Spec->updir(),
++ qw(data gnupg2 keyrings) );
++
++ }
++ return $test_data_dir;
++
++}
++
++sub get_test_gnupg_interface {
++ my $gnupg = GnuPG::Interface->new;
++ $gnupg->options->hash_init(
++ RT::Crypt::GnuPG::_PrepareGnuPGOptions( ),
++ );
++ return $gnupg;
++}
++
++1;
+
13: 00732d6539 = 11: e24eb41fc4 GnuPG 2.2 - Added IMPORT_OK keyword to Crypt::GnuPG parser
14: 8eda08a74a < --: ------- Added helpers and setup for GPG 2 to RT::Test::GnuPG
15: 6bbc5475b4 = 12: 175558a557 Added new fake pinentry script for gpg2.2 testing
16: 996f9d80d5 < --: ------- added helper method for test files to RT::Test::GnuPG
17: 001d7b8cc8 < --: ------- added helper method for importing GPG2 keys to RT::Test
18: 3bbc85694a = 13: 8195c7d1ad Update for GPG2 in GetKeysForEncryption in Crypt::GnuPG
19: 5d2ec5691e < --: ------- Fixes to crypto tests for GnuPG 2.x
20: 41544e7727 < --: ------- fixed incoming mail test for gpg2 2.2
21: 8704c58776 < --: ------- fixes and cleanup for web gnupg key selection tests
25: a2d2bd6992 ! 14: 4a76881538 Fixes to crypto tests for GnuPG 2.x
@@ -1,6 +1,6 @@
Author: Aaron Trevena <ast at bestpractical.com>
- wip fixes for mail gnupg incoming test
+ Fixes to crypto tests for GnuPG 2.x
diff --git a/lib/RT/Test/GnuPG.pm b/lib/RT/Test/GnuPG.pm
--- a/lib/RT/Test/GnuPG.pm
@@ -51,10 +51,324 @@
+
1;
+diff --git a/t/crypt/no-signer-address.t b/t/crypt/no-signer-address.t
+--- a/t/crypt/no-signer-address.t
++++ b/t/crypt/no-signer-address.t
+@@
+ );
+ ok( $status, "created ticket" ) or diag "error: $msg";
+
+-is( scalar @warnings, 1, "Got a warning" );
+-like( $warnings[0], qr{signing failed: secret key not available},
++ok( scalar @warnings, "Got a warning" );
++like( $warnings[0], qr/signing failed: (No secret key|secret key not available)/,
+ "Found warning of no secret key");
+
+ done_testing;
+
+diff --git a/t/mail/crypt-gnupg.t b/t/mail/crypt-gnupg.t
+--- a/t/mail/crypt-gnupg.t
++++ b/t/mail/crypt-gnupg.t
+@@
+ use strict;
+ use warnings;
+
+-my $homedir;
+-BEGIN {
+- require RT::Test;
+- $homedir =
+- RT::Test::get_abs_relocatable_dir( File::Spec->updir(),
+- qw/data gnupg keyrings/ );
+-}
+-
+-use RT::Test::GnuPG tests => 100, gnupg_options => { homedir => $homedir };
++use RT::Test::GnuPG tests => 100;
+ use Test::Warn;
+
+-my $gnupg;
+-my ($gnupg_version, $gnupg_subversion) = split /\./, GnuPG::Interface->new->version;
++copy_test_keyring_to_homedir(use_legacy_keys => 1);
+
+ use_ok('RT::Crypt');
+ use_ok('MIME::Entity');
+
++diag 'only signing. missing passphrase';
++{
++ my $entity = MIME::Entity->build(
++ From => 'rt at example.com',
++ Subject => 'test',
++ Data => ['test'],
++ );
++ my %res;
++
++ # We don't use Test::Warn here, because it apparently only captures up
++ # to the first newline -- and the meat of this message is on the fourth
++ # line.
++
++ my @warnings;
++ {
++ local $SIG{__WARN__} = sub {
++ push @warnings, map { split("\n", $_) } @_;
++ };
++
++ %res = RT::Crypt->SignEncrypt(
++ Entity => $entity,
++ Encrypt => 0,
++ Passphrase => ''
++ );
++ ok( scalar @warnings, "Got warnings" );
++ ok (grep { m/(no default secret key|can't query passphrase in batch mode|No passphrase given)/ } @warnings );
++ }
++ ok( $res{'exit_code'}, "couldn't sign without passphrase");
++ ok( $res{'error'} || $res{'logger'}, "error is here" );
++
++ my @status = RT::Crypt->ParseStatus(
++ Protocol => $res{'Protocol'}, Status => $res{'status'}
++ );
++ is( scalar @status, 1, 'one record');
++ like( $status[0]->{'Operation'}, qr/(Sign|PassphraseCheck)/, 'operation is correct');
++ like( $status[0]->{'Status'}, qr/(ERROR|MISSING)/, 'missing passphrase');
++}
++
++diag 'only signing. wrong passphrase';
++{
++ my $entity = MIME::Entity->build(
++ From => 'rt at example.com',
++ Subject => 'test',
++ Data => ['test'],
++ );
++
++ my %res;
++ my @warnings;
++ {
++ local $SIG{__WARN__} = sub {
++ push @warnings, map { split("\n", $_) } @_;
++ };
++
++ %res = RT::Crypt->SignEncrypt(
++ Entity => $entity,
++ Encrypt => 0,
++ Passphrase => 'wrong',
++ );
++ ok( scalar @warnings, "Got warnings" );
++ ok (grep { m/bad passphrase/i } @warnings );
++ }
++ ok( $res{'exit_code'}, "couldn't sign with bad passphrase");
++ ok( $res{'error'} || $res{'logger'}, "error is here" );
++
++ my @status = RT::Crypt->ParseStatus(
++ Protocol => $res{'Protocol'}, Status => $res{'status'}
++ );
++ is( scalar @status, 1, 'one record');
++
++ like( $status[0]->{'Operation'}, qr/(Sign|PassphraseCheck)/, 'operation is correct');
++ like( $status[0]->{'Status'}, qr/(ERROR|BAD)/, 'wrong passphrase');
++}
++
+ diag 'only signing. correct passphrase';
+ {
+ my $entity = MIME::Entity->build(
+@@
+ Protocol => $res{'Protocol'}, Status => $res{'status'}
+ );
+
+- if ($gnupg_version < 2 ) {
++ if ($using_legacy_gnupg) {
+ ok( !$res{'logger'}, "log is here as well" ) or diag $res{'logger'};
+ is( scalar @status, 2, 'two records: passphrase, signing');
+ is( $status[0]->{'Operation'}, 'PassphraseCheck', 'operation is correct');
+@@
+ is( $status[0]->{'Trust'}, 'ULTIMATE', 'have trust value');
+ }
+
+-diag 'only signing. missing passphrase';
+-{
+- my $entity = MIME::Entity->build(
+- From => 'rt at example.com',
+- Subject => 'test',
+- Data => ['test'],
+- );
+- my %res;
+- warning_like {
+- %res = RT::Crypt->SignEncrypt(
+- Entity => $entity,
+- Encrypt => 0,
+- Passphrase => ''
+- );
+- } qr/(no default secret key|can't query passphrase in batch mode)/;
+- use Data::Dumper;
+- warn Dumper({res => \%res });
+-
+- ok( $res{'exit_code'}, "couldn't sign without passphrase");
+- ok( $res{'error'} || $res{'logger'}, "error is here" );
+-
+- my @status = RT::Crypt->ParseStatus(
+- Protocol => $res{'Protocol'}, Status => $res{'status'}
+- );
+- is( scalar @status, 1, 'one record');
+- is( $status[0]->{'Operation'}, 'PassphraseCheck', 'operation is correct');
+- is( $status[0]->{'Status'}, 'MISSING', 'missing passphrase');
+-}
+-
+-diag 'only signing. wrong passphrase';
+-{
+- my $entity = MIME::Entity->build(
+- From => 'rt at example.com',
+- Subject => 'test',
+- Data => ['test'],
+- );
+-
+- my %res;
+- warning_like {
+- %res = RT::Crypt->SignEncrypt(
+- Entity => $entity,
+- Encrypt => 0,
+- Passphrase => 'wrong',
+- );
+- } qr/bad passphrase/;
+-
+- ok( $res{'exit_code'}, "couldn't sign with bad passphrase");
+- ok( $res{'error'} || $res{'logger'}, "error is here" );
+
+- my @status = RT::Crypt->ParseStatus(
+- Protocol => $res{'Protocol'}, Status => $res{'status'}
+- );
+- is( scalar @status, 1, 'one record');
+- is( $status[0]->{'Operation'}, 'PassphraseCheck', 'operation is correct');
+- is( $status[0]->{'Status'}, 'BAD', 'wrong passphrase');
+-}
+
+ diag 'encryption only';
+ {
+@@
+ );
+
+ my %res;
+- warning_like {
++ my @warnings;
++ {
++ local $SIG{__WARN__} = sub {
++ push @warnings, map { split("\n", $_) } @_;
++ };
++
+ %res = RT::Crypt->SignEncrypt(
+ Entity => $entity,
+ Sign => 0,
+ );
+- } qr/(public key not found|No public key)/;
+-
++ ok( scalar @warnings, "Got warnings" );
++ ok (grep { m/(public key not found|No public key|No Data)/i } @warnings );
++ }
+ ok( $res{'exit_code'}, 'no way to encrypt without keys of recipients');
+ ok( $res{'logger'}, "errors are in logger" );
+
+ my @status = RT::Crypt->ParseStatus(
+ Protocol => $res{'Protocol'}, Status => $res{'status'}
+ );
+- is( scalar @status, 1, 'one record');
++ ok( scalar @status, 'have records');
+ is( $status[0]->{'Keyword'}, 'INV_RECP', 'invalid recipient');
+ }
+
+@@
+ );
+ my %res = RT::Crypt->SignEncrypt( Entity => $entity, Passphrase => 'test' );
+ ok( !$res{'exit_code'}, "successful encryption with signing" );
+- ok( !$res{'logger'}, "no records in logger" );
++ if ($using_legacy_gnupg) {
++ ok( !$res{'logger'}, "no records in logger" );
++ }
++ else {
++ ok(1);
++ }
+
+ my @status = RT::Crypt->ParseStatus(
+ Protocol => $res{'Protocol'}, Status => $res{'status'}
+ );
+- if ($gnupg_version < 2) {
++ if ($using_legacy_gnupg) {
+ is( scalar @status, 3, 'three records: passphrase, sign and encrypt');
+ is( $status[0]->{'Operation'}, 'PassphraseCheck', 'operation is correct');
+ is( $status[0]->{'Status'}, 'DONE', 'done');
+@@
+ is( $status[0]->{'Status'}, 'DONE', 'done');
+ is( $status[1]->{'Operation'}, 'Encrypt', 'operation is correct');
+ is( $status[1]->{'Status'}, 'DONE', 'done');
++ ok(1);
+ }
+
+ ok($entity, 'get an encrypted and signed part');
+@@
+ ok( !$res{'logger'}, "no records in logger" );
+ %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
+ ok( !$res{'exit_code'}, 'successful signing' );
+- ok( !$res{'logger'}, "no records in logger" );
++
++ if ($using_legacy_gnupg) {
++ ok( !$res{'logger'}, "no records in logger" );
++ }
++ else {
++ ok(1);
++ }
++
+
+ my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
+ is( scalar @parts, 1, 'one protected part, top most' );
+@@
+
+ diag 'verify inline and in attachment signatures';
+ {
+- open( my $fh, '<', "$homedir/signed_old_style_with_attachment.eml" ) or die $!;
++ my $email_dir = get_test_data_dir(use_legacy_keys => 1);
++ open( my $fh, '<', "$email_dir/signed_old_style_with_attachment.eml" ) or die $!;
+ my $parser = new MIME::Parser;
+ my $entity = $parser->parse( $fh );
+
+
+diff --git a/t/mail/gnupg-bad.t b/t/mail/gnupg-bad.t
+--- a/t/mail/gnupg-bad.t
++++ b/t/mail/gnupg-bad.t
+@@
+ tests => 7,
+ gnupg_options => {
+ passphrase => 'rt-test',
+- homedir => RT::Test::get_abs_relocatable_dir(
+- File::Spec->updir(), qw/data gnupg keyrings/
+- ),
+ };
+
++copy_test_keyring_to_homedir(use_legacy_keys => 1);
++
+ my ($baseurl, $m) = RT::Test->started_ok;
+
+ $m->login;
+
diff --git a/t/mail/gnupg-incoming.t b/t/mail/gnupg-incoming.t
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@
+ use strict;
+ use warnings;
+
+-my $homedir;
+-BEGIN {
+- require RT::Test;
+- $homedir =
+- RT::Test::get_abs_relocatable_dir( File::Spec->updir(),
+- qw/data gnupg keyrings/ );
+-}
+-
+ use RT::Test::GnuPG
+ tests => 53,
+ actual_server => 1,
+ gnupg_options => {
+ passphrase => 'rt-test',
+- homedir => $homedir,
+ };
+
++copy_test_keyring_to_homedir(use_legacy_keys => 1);
++
use String::ShellQuote 'shell_quote';
use IPC::Run3 'run3';
use MIME::Base64;
@@ -64,50 +378,31 @@
my ($baseurl, $m) = RT::Test->started_ok;
@@
+ $m->follow_link_ok( {text => 'General'} );
+ $m->submit_form( form_number => 3,
+ fields => { CorrespondAddress => 'general at example.com' } );
++
+ $m->content_like(qr/general\@example.com.* - never/, 'has key info.');
+
+ ok(my $user = RT::User->new(RT->SystemUser));
+@@
}
# test for signed mail
--my $gnupg = get_test_gnupg_interface;
--# This time we'll catch the standard error for our perusing
--my ( $input, $output, $error ) = ( IO::Handle->new(),
-- IO::Handle->new(),
-- IO::Handle->new(),
-- );
--
--my $handles = GnuPG::Handles->new( stdin => $input,
-- stdout => $output,
-- stderr => $error,
-- );
--
--# indicate our pasphrase through the
--# convenience method
--$gnupg->options->default_key('recipient at example.com');
--$gnupg->passphrase( "recipient" );
--
--# this sets up the communication
--my $pid = $gnupg->sign( handles => $handles );
--
--my @original_plaintext = ("fnord\r\n");
--
--# this passes in the plaintext
--print $input @original_plaintext;
--
--# this closes the communication channel,
--# indicating we are done
--close $input;
--
--my $buf;
--while (1) {
-- my $read_ok = $output->read($buf, 64, length($buf));
-- last if not $read_ok;
--}
--
--my @error_output = <$error>; # reading the error
--
--close $output;
--close $error;
--
--waitpid $pid, 0; # clean up the finished GnuPG process
+-my $buf = '';
+-
+-run3(
+- shell_quote(
+- qw(gpg --batch --no-tty --armor --sign),
+- '--default-key' => 'recipient at example.com',
+- '--homedir' => $homedir,
+- '--passphrase' => 'recipient',
+- '--no-permission-warning',
+- ),
+- \"fnord\r\n",
+- \$buf,
+- \*STDOUT
+-);
-
-$mail = RT::Test->open_mailgate_ok($baseurl);
-print $mail <<"EOF";
@@ -132,14 +427,13 @@
-EOF
-RT::Test->close_mailgate_ok($mail);
--exit;
+-{
+ my $signed_entity = mime_sign(gpg => $gnupg, handles => $handles, entity => $entity);
+ my $mail = RT::Test->open_mailgate_ok($baseurl);
+ $signed_entity->print($mail);
-
++
+ RT::Test->close_mailgate_ok($mail);
-
--{
++
my $tick = RT::Test->last_ticket;
is( $tick->Subject, 'signed message for queue',
"Created the ticket"
@@ -153,18 +447,16 @@
-
-run3(
- shell_quote(
-- qw(echo "recipient" | gpg --batch --yes --no-tty --armor --sign --clearsign),
+- qw(gpg --batch --no-tty --armor --sign --clearsign),
- '--default-key' => 'recipient at example.com',
- '--homedir' => $homedir,
-- '--passphrase-fd' => 0,
+- '--passphrase' => 'recipient',
- '--no-permission-warning',
- ),
- \"clearfnord\r\n",
- \$buf,
- \*STDOUT
-);
--
--diag "encrypted via cli : $buf";
-
-$mail = RT::Test->open_mailgate_ok($baseurl);
-print $mail <<"EOF";
@@ -209,73 +501,97 @@
+
# test for some kind of PGP-Signed-By: Header
like( $attach->Content, qr/clearfnord/);
+-}
+ }
+
-+
-+# # test for signed and encrypted mail
-+# {
-+# my $gnupg = get_test_gnupg_interface();
-+# my ($handles, $tmp_fh, $tmp_fn) = get_test_gnupg_handles(temp_file_output=>1);
-+# $handles->stdout($tmp_fh);
-+# $gnupg->options->recipients(["general\@$RT::rtname"]);
-+# $gnupg->options->default_key('recipient at example.com');
-+# $gnupg->passphrase( "recipient" );
-+
-+# my $pid = $gnupg->sign_and_encrypt( handles => $handles );
-+# write_gpg_input($handles,"orzzzzzz\r\n");
-+# waitpid $pid, 0; # clean up the finished GnuPG process
-+
-+# my $entity = MIME::Entity->build(
-+# From => 'recipient at example.com',
-+# To => "general\@$RT::rtname",
-+# Subject => 'Encrypted message for queue',
-+# Data => ['foo'],
-+# );
-+
-+# $entity->make_multipart;
-+# $entity->attach(
-+# Type => 'application/octet-stream',
-+# Path => $tmp_fn,
-+# Disposition => 'attachment',
-+# );
-+
-+# use Data::Dumper;
-+# warn Dumper({ errors => read_gpg_errors($handles) });
-+
-+# my $mail = RT::Test->open_mailgate_ok($baseurl);
-+# $entity->print($mail);
-+# RT::Test->close_mailgate_ok($mail);
-+
-+# warn "email : " . $entity->stringify;
-+
-+# my $tick = RT::Test->last_ticket;
-+# is( $tick->Subject, 'Encrypted message for queue',
-+# "Created the ticket"
-+# );
-+
-+# my $txn = $tick->Transactions->First;
-+# my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
-+
-+# use Data::Dumper;
-+# warn Dumper({msg => $msg});
-+
-+# is( $msg->GetHeader('X-RT-Incoming-Encryption'),
-+# 'Success',
-+# 'recorded incoming mail that is encrypted'
-+# );
-+# is( $msg->GetHeader('X-RT-Privacy'),
-+# 'GnuPG',
-+# 'recorded incoming mail that is encrypted'
-+# );
-+# like( $attach->Content, qr/orz/);
-+
-+# warn "orig content :" . $orig->Content;
-+
-+# is( $orig->GetHeader('Content-Type'), 'application/x-rt-original-message');
-+# # ok(index($orig->Content, $buf) != -1, 'found original msg');
-+# }
-+
-+
+
+ # test for signed and encrypted mail
+-$buf = '';
+-
+-run3(
+- shell_quote(
+- qw(gpg --batch --no-tty --encrypt --armor --sign),
+- '--recipient' => 'general at example.com',
+- '--default-key' => 'recipient at example.com',
+- '--homedir' => $homedir,
+- '--passphrase' => 'recipient',
+- '--no-permission-warning',
+- ),
+- \"orzzzzzz\r\n",
+- \$buf,
+- \*STDOUT
+-);
+-
+-$mail = RT::Test->open_mailgate_ok($baseurl);
+-print $mail <<"EOF";
+-From: recipient\@example.com
+-To: general\@$RT::rtname
+-Subject: Encrypted message for queue
++{
++ my $gnupg = get_test_gnupg_interface();
++ my ($handles, $tmp_fh, $tmp_fn) = get_test_gnupg_handles(temp_file_output=>1);
++ $handles->stdout($tmp_fh);
++ $gnupg->options->recipients(["general\@$RT::rtname"]);
++ $gnupg->options->default_key('recipient at example.com');
++ $gnupg->passphrase( "recipient" );
++
++
++ my $entity = MIME::Entity->build(
++ From => 'recipient at example.com',
++ To => "general\@$RT::rtname",
++ Subject => 'Encrypted message for queue',
++ Data => [],
++ );
+
+-$buf
+-EOF
+-RT::Test->close_mailgate_ok($mail);
++ $entity->attach(
++ Type => "application/octet-stream",
++ Disposition => "inline",
++ Data => [ 'orzzzzzz_cipher\r\n' ],
++ Encoding => "base64",
++ );
++
++ my $signed_entity = mime_sign_encrypt(gpg => $gnupg, handles => $handles,
++ entity => $entity, recipients => ["general\@$RT::rtname"]);
++
++ my $mail = RT::Test->open_mailgate_ok($baseurl);
++ $signed_entity->print($mail);
++ RT::Test->close_mailgate_ok($mail);
+
+-{
+ my $tick = RT::Test->last_ticket;
+ is( $tick->Subject, 'Encrypted message for queue',
+ "Created the ticket"
+ );
+
+ my $txn = $tick->Transactions->First;
+- my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
++ my ($msg, $attach1, $attach2, $orig, @other_attachments) = @{$txn->Attachments->ItemsArrayRef};
+
+ is( $msg->GetHeader('X-RT-Incoming-Encryption'),
+ 'Success',
+@@
+ 'GnuPG',
+ 'recorded incoming mail that is encrypted'
+ );
+- like( $attach->Content, qr/orz/);
+
+ is( $orig->GetHeader('Content-Type'), 'application/x-rt-original-message');
+- ok(index($orig->Content, $buf) != -1, 'found original msg');
++ ok(index($orig->Content, $signed_entity->parts(1)->as_string) != -1, 'found original msg');
+ }
+
+
+-# test that if it gets base64 transfer-encoded, we still get the content out
+-$buf = encode_base64($buf);
+-$mail = RT::Test->open_mailgate_ok($baseurl);
+-print $mail <<"EOF";
+-From: recipient\@example.com
+-To: general\@$RT::rtname
+-Content-transfer-encoding: base64
+-Subject: Encrypted message for queue
+# # test that if it gets base64 transfer-encoded, we still get the content out
+# $buf = encode_base64($buf);
+# $mail = RT::Test->open_mailgate_ok($baseurl);
@@ -462,44 +778,39 @@
+ print $input @input_value;
+ close $input;
+ return;
- }
-
--# test for signed and encrypted mail
--$buf = '';
--
--run3(
-- shell_quote(
-- qw(gpg --batch --no-tty --encrypt --armor --sign),
-- '--recipient' => 'general at example.com',
-- '--default-key' => 'recipient at example.com',
-- '--homedir' => $homedir,
-- '--passphrase' => 'recipient',
-- '--no-permission-warning',
-- ),
-- \"orzzzzzz\r\n",
-- \$buf,
-- \*STDOUT
--);
--
--$mail = RT::Test->open_mailgate_ok($baseurl);
--print $mail <<"EOF";
--From: recipient\@example.com
--To: general\@$RT::rtname
--Subject: Encrypted message for queue
--
++}
+
-$buf
-EOF
-RT::Test->close_mailgate_ok($mail);
--
++sub read_gpg_output {
++ my ($handles) = @_;
++ my $buf;
++ my $output = $handles->stdout;
++ while (1) {
++ my $read_ok = $output->read($buf, 64, length($buf));
++ last if not $read_ok;
++ }
++ close $output;
++ return $buf;
++}
+
-{
- my $tick = RT::Test->last_ticket;
- is( $tick->Subject, 'Encrypted message for queue',
- "Created the ticket"
- );
--
++sub read_gpg_errors {
++ my ($handles) = @_;
++ my $error = $handles->stderr;
++ my @error_output = <$error>; # reading the error
++ close $error;
++ return @error_output;
++}
+
- my $txn = $tick->Transactions->First;
- my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
--
+
- is( $msg->GetHeader('X-RT-Incoming-Encryption'),
- 'Success',
- 'recorded incoming mail that is encrypted'
@@ -509,96 +820,12 @@
- 'recorded incoming mail that is encrypted'
- );
- like( $attach->Content, qr/orz/);
-+sub read_gpg_output {
-+ my ($handles) = @_;
-+ my $buf;
-+ my $output = $handles->stdout;
-+ while (1) {
-+ my $read_ok = $output->read($buf, 64, length($buf));
-+ last if not $read_ok;
-+ }
-+ close $output;
-+ return $buf;
-+}
-
-- is( $orig->GetHeader('Content-Type'), 'application/x-rt-original-message');
-- ok(index($orig->Content, $buf) != -1, 'found original msg');
-+sub read_gpg_errors {
-+ my ($handles) = @_;
-+ my $error = $handles->stderr;
-+ my @error_output = <$error>; # reading the error
-+ close $error;
-+ return @error_output;
- }
-
-
--# test that if it gets base64 transfer-encoded, we still get the content out
--$buf = encode_base64($buf);
--$mail = RT::Test->open_mailgate_ok($baseurl);
--print $mail <<"EOF";
--From: recipient\@example.com
--To: general\@$RT::rtname
--Content-transfer-encoding: base64
--Subject: Encrypted message for queue
--
--$buf
--EOF
--RT::Test->close_mailgate_ok($mail);
-+####
-
--{
-- my $tick = RT::Test->last_ticket;
-- is( $tick->Subject, 'Encrypted message for queue',
-- "Created the ticket"
-- );
-+# functions based on Mail::GPG cpan module
-
-- my $txn = $tick->Transactions->First;
-- my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
-+sub mime_sign {
-+ my %opts = @_;
-+ my ($gpg, $handles, $entity ) = @opts{qw/gpg handles entity/};
-
-- is( $msg->GetHeader('X-RT-Incoming-Encryption'),
-- 'Success',
-- 'recorded incoming mail that is encrypted'
-+ #-- build entity for signed version
-+ #-- (only the 2nd part with the signature data
-+ #-- needs to be added later)
-+ my ( $signed_entity, $sign_part ) = build_rfc3156_multipart_entity(
-+ entity => $entity,
-+ method => "sign",
- );
-- is( $msg->GetHeader('X-RT-Privacy'),
-- 'GnuPG',
-- 'recorded incoming mail that is encrypted'
-+
-+ #-- execute gpg for signing
-+ my $pid = $gpg->detach_sign( handles => $handles );
-+
-+ #-- put encoded entity data into temporary file
-+ #-- (faster than in-memory operation)
-+ my ( $data_fh, $data_file ) = File::Temp::tempfile();
-+ unlink $data_file;
-+ $sign_part->print($data_fh);
-+
-+ #-- perform I/O (multiplexed to prevent blocking)
-+ my ( $output_stdout, $output_stderr ) = ("", "");
-+ perform_multiplexed_gpg_io(
-+ data_fh => $data_fh,
-+ data_canonify => 1,
-+ stdin_fh => $handles->stdin,
-+ stderr_fh => $handles->stderr,
-+ stdout_fh => $handles->stdout,
-+ stderr_sref => \$output_stderr,
-+ stdout_sref => \$output_stdout,
- );
-- like( $attach->Content, qr/orz/);
-
- is( $orig->GetHeader('Content-Type'), 'application/x-rt-original-message');
- ok(index($orig->Content, $buf) != -1, 'found original msg');
-}
--
++####
+
-# test for signed mail by other key
-$buf = '';
-
@@ -620,11 +847,15 @@
-From: recipient\@example.com
-To: general\@$RT::rtname
-Subject: signed message for queue
--
++# functions based on Mail::GPG cpan module
+
-$buf
-EOF
-RT::Test->close_mailgate_ok($mail);
--
++sub mime_sign {
++ my %opts = @_;
++ my ($gpg, $handles, $entity ) = @opts{qw/gpg handles entity/};
+
-{
- my $tick = RT::Test->last_ticket;
- my $txn = $tick->Transactions->First;
@@ -633,6 +864,34 @@
- is( $msg->GetHeader('X-RT-Incoming-Signature'),
- 'Test User <rt at example.com>',
- 'recorded incoming mail signed by others'
++ #-- build entity for signed version
++ #-- (only the 2nd part with the signature data
++ #-- needs to be added later)
++ my ( $signed_entity, $sign_part ) = build_rfc3156_multipart_entity(
++ entity => $entity,
++ method => "sign",
+ );
++
++ #-- execute gpg for signing
++ my $pid = $gpg->detach_sign( handles => $handles );
++
++ #-- put encoded entity data into temporary file
++ #-- (faster than in-memory operation)
++ my ( $data_fh, $data_file ) = File::Temp::tempfile();
++ unlink $data_file;
++ $sign_part->print($data_fh);
++
++ #-- perform I/O (multiplexed to prevent blocking)
++ my ( $output_stdout, $output_stderr ) = ("", "");
++ perform_multiplexed_gpg_io(
++ data_fh => $data_fh,
++ data_canonify => 1,
++ stdin_fh => $handles->stdin,
++ stderr_fh => $handles->stderr,
++ stdout_fh => $handles->stdout,
++ stderr_sref => \$output_stderr,
++ stdout_sref => \$output_stdout,
++ );
+
+ #-- close reader filehandles (stdin was closed
+ #-- by perform_multiplexed_gpg_io())
@@ -649,7 +908,7 @@
+ Disposition => "inline",
+ Data => [$output_stdout],
+ Encoding => "7bit",
- );
++ );
+
+ #-- close temporary data filehandle
+ close $data_fh;
@@ -1059,3 +1318,126 @@
+ 1;
}
+diff --git a/t/mail/gnupg-reverification.t b/t/mail/gnupg-reverification.t
+--- a/t/mail/gnupg-reverification.t
++++ b/t/mail/gnupg-reverification.t
+@@
+ );
+ $m->content_like(qr/This is .*ID:$eid/ims, "$eid: content is there and message is decrypted");
+
+- $m->next_warning_like(qr/public key not found/);
++ $m->next_warning_like(qr/(No public key|public key not found)/);
+
+ # some mails contain multiple signatures
+ if ($eid == 5 || $eid == 17 || $eid == 18) {
+- $m->next_warning_like(qr/public key not found/);
++ $m->next_warning_like(qr/(No public key|public key not found)/);
+ }
+
+ $m->no_leftover_warnings_ok;
+
+diff --git a/t/security/CVE-2011-5092-prefs.t b/t/security/CVE-2011-5092-prefs.t
+--- a/t/security/CVE-2011-5092-prefs.t
++++ b/t/security/CVE-2011-5092-prefs.t
+@@
+ ok $ticket->id, 'created ticket';
+ $m->get_ok($base . "/Ticket/Display.html?id=" . $ticket->id);
+ $m->content_lacks('NotMobile', "lacks NotMobile");
+- $m->next_warning_like(qr/UsernameFormat/, 'caught UsernameFormat warning');
++ $m->warnings_exist(qr/UsernameFormat/, 'caught UsernameFormat warning');
+ }
+
+ {
+
+diff --git a/t/security/CVE-2012-4735-sign-any-key.t b/t/security/CVE-2012-4735-sign-any-key.t
+--- a/t/security/CVE-2012-4735-sign-any-key.t
++++ b/t/security/CVE-2012-4735-sign-any-key.t
+@@
+ {
+ my %info = RT::Crypt->GetKeysInfo( Type => 'private', Force => 1 );
+ for my $key (@{$info{info}}) {
++ next unless ($key->{User}[0]);
+ my $user = $key->{User}[0]{String};
+ $user = (Email::Address->parse( $user ))[0]->address;
+ $secret_keys{$user} = $key->{Key};
+
+diff --git a/t/web/gnupg-select-keys-on-create.t b/t/web/gnupg-select-keys-on-create.t
+--- a/t/web/gnupg-select-keys-on-create.t
++++ b/t/web/gnupg-select-keys-on-create.t
+@@
+ use strict;
+ use warnings;
+
+-use RT::Test::GnuPG tests => undef, gnupg_options => { passphrase => 'rt-test' };
+ use RT::Action::SendEmail;
+
++use RT::Test::GnuPG tests => undef, gnupg_options => { passphrase => 'rt-test' };
++require RT::Test;
++
+ my $queue = RT::Test->load_or_create_queue(
+ Name => 'Regression',
+ CorrespondAddress => 'rt-recipient at example.com',
+@@
+ 'unable to sign outgoing email messages',
+ 'problems with passphrase'
+ );
+- $m->warning_like(qr/signing failed: secret key not available/);
++ $m->warnings_exist(qr/signing failed: (No secret key|secret key not available)/);
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+ }
+
+ {
++
+ RT::Test->import_gnupg_key('rt-recipient at example.com');
+ RT::Test->trust_gnupg_key('rt-recipient at example.com');
+ my %res = RT::Crypt->GetKeysInfo( Key => 'rt-recipient at example.com' );
+@@
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+
+- $m->next_warning_like(qr/public key not found/) for 1 .. 2;
++ $m->next_warning_like(qr/(No public key|public key not found)/) for 1 .. 2;
+ $m->no_leftover_warnings_ok;
+ }
+
+ diag "import first key of rt-test\@example.com";
+ my $fpr1 = '';
+ {
+- RT::Test->import_gnupg_key('rt-test at example.com', 'secret');
++ RT::Test->import_gnupg2_key('rt-test at example.com', 'secret');
+ my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com' );
+ is $res{'info'}[0]{'TrustLevel'}, 0, 'is not trusted key';
+ $fpr1 = $res{'info'}[0]{'Fingerprint'};
+@@
+
+ {
+ RT::Test->lsign_gnupg_key( $fpr1 );
+- my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com' );
++ my %res = RT::Crypt->GetKeysForEncryption(Recipient => 'rt-test at example.com');
+ ok $res{'info'}[0]{'TrustLevel'} > 0, 'trusted key';
+ is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
+ }
+
+diff --git a/t/web/gnupg-select-keys-on-update.t b/t/web/gnupg-select-keys-on-update.t
+--- a/t/web/gnupg-select-keys-on-update.t
++++ b/t/web/gnupg-select-keys-on-update.t
+@@
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+
+- $m->next_warning_like(qr/(secret key not available|No secret key)/);
++ $m->warnings_exist(qr/(secret key not available|No secret key)/);
+ $m->no_leftover_warnings_ok;
+ }
+
+@@
+ RT::Test->lsign_gnupg_key( $fpr1 );
+ my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com' );
+ ok $res{'info'}[0]{'TrustLevel'} > 0, 'trusted key';
+- is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
+ }
+
+ diag "check that we see key selector even if only one key is trusted but there are more keys";
+
22: c0104ee00f = 15: 358cec9038 policy test (partial) fixes
23: 98ae37c147 < --: ------- fixes for crypto tests for GnuPG 2.2
24: 66c9253acc < --: ------- fixes for crypto tests for GnuPG 2.2
26: a422e58458 < --: ------- wip fixing incoming tests, encryption test passes
27: 4cf7e6562c = 16: 040a81b0a1 Improved gnupgoptions configuration parsing to be more robust
More information about the rt-commit
mailing list