1#!/usr/local/bin/perl 2 3# caff -- CA - Fire and Forget 4# 5# Copyright (c) 2004, 2005, 2006 Peter Palfrader <peter@palfrader.org> 6# Copyright (c) 2005, 2006 Christoph Berg <cb@df7cb.de> 7# Copyright (c) 2014-2016 Guilhem Moulin <guilhem@debian.org> 8# 9# All rights reserved. 10# 11# Redistribution and use in source and binary forms, with or without 12# modification, are permitted provided that the following conditions 13# are met: 14# 1. Redistributions of source code must retain the above copyright 15# notice, this list of conditions and the following disclaimer. 16# 2. Redistributions in binary form must reproduce the above copyright 17# notice, this list of conditions and the following disclaimer in the 18# documentation and/or other materials provided with the distribution. 19# 3. The name of the author may not be used to endorse or promote products 20# derived from this software without specific prior written permission. 21# 22# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 23# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 26# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 27# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 31# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 33=pod 34 35=head1 NAME 36 37caff -- CA - Fire and Forget 38 39=head1 SYNOPSIS 40 41=over 42 43=item B<caff> [-eERS] [-m I<yes|ask-yes|ask-no|no>] [-u I<yourkeyid>] I<keyid> [I<keyid> ..] 44 45=item B<caff> [-eERS] [-m I<yes|ask-yes|ask-no|no>] [-u I<yourkeyid>] [I<keyid> ..] <I</path/to/ksp-annotated.txt> 46 47=back 48 49=head1 DESCRIPTION 50 51CA Fire and Forget is a script that helps you in keysigning. It takes a list 52of keyids on the command line, fetches them from a keyserver and calls GnuPG so 53that you can sign it. It then mails each key to all its email addresses - only 54including the one UID that we send to in each mail, pruned from all but self 55sigs and sigs done by you. The mailed key is encrypted with itself as a means 56to verify that key belongs to the recipient. 57 58The list of keys to sign can also be provided through caff's standard 59input, as gpgparticipants(1) formatted content. Only keys for which 60both the "Fingerprint OK" and "ID OK" boxes are ticked (i.e., marked 61with an "x") are considered for signing. Furthermore, the input header 62must include at least one checksum line, and all checksum boxes must be 63marked as verified (with an "x"). 64 65=head1 OPTIONS 66 67=over 68 69=item B<-e>, B<--export-old> 70 71Export old signatures. Default is to ask the user for each old signature. 72 73=item B<-E>, B<--no-export-old> 74 75Do not export old signatures. Default is to ask the user for each old 76signature. 77 78=item B<-m>, B<--mail> I<yes|ask-yes|ask-no|no> 79 80Whether to send mail after signing. Default is to ask, for each uid, 81with a default value of yes. 82 83=item B<-R>, B<--no-download> 84 85Do not retrieve the key to be signed from a keyserver. 86 87=item B<-S>, B<--no-sign> 88 89Do not sign the keys. 90 91=item B<-u> I<yourkeyid>, B<--local-user> I<yourkeyid> 92 93Select the key that is used for signing, in case you have more than one key. 94To sign with multiple keys at once, separate multiple keyids by comma. This 95option requires the key(s) to be defined through the keyid variable in the 96configuration file. 97 98=item B<--key-file> I<file> 99 100Import keys from file. Can be supplied more than once. 101 102=item B<--keys-from-gnupg> 103 104Try to import keys from your standard GnuPG keyrings. 105 106=item B<--debug> 107 108Enable debug messages. 109 110=back 111 112=head1 ENVIRONMENT 113 114=over 115 116=item I<HOME> 117 118The default home directory. 119 120=item I<GNUPGBIN> 121 122The gpg binary. Default: C<"gpg">. 123 124=item I<GNUPGHOME> 125 126The default working directory for gpg. Default: C<$HOME/.gnupg>. 127 128=back 129 130=head1 FILES 131 132=over 133 134=item $HOME/.caffrc - configuration file 135 136=item $HOME/.caff/keys/yyyy-mm-dd/ - processed keys 137 138=item $HOME/.caff/gnupghome/ - caff's working directory for gpg 139 140=item $HOME/.caff/gnupghome/gpg.conf - gpg configuration (see B<NOTES> below) 141 142useful options include use-agent, keyserver, keyserver-options, default-cert-level, etc. 143 144=back 145 146=head1 CONFIGURATION FILE OPTIONS 147 148The configuration file is a perl script that sets values in the hash B<%CONFIG>. 149The file is generated when it does not exist. 150 151Example: 152 153 $CONFIG{'owner'} = q{Peter Palfrader}; 154 $CONFIG{'email'} = q{peter@palfrader.org}; 155 $CONFIG{'keyid'} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ]; 156 157=head2 Required basic settings 158 159=over 160 161=item B<owner> [string] 162 163Your name. B<REQUIRED>. 164 165=item B<email> [string] 166 167Your email address, used in From: lines. B<REQUIRED>. 168 169=item B<keyid> [list of keyids] 170 171A list of your keys. This is used to determine which signatures to keep 172in the pruning step. If you select a key using B<-u> it has to be in 173this list. B<REQUIRED>. 174 175=back 176 177=head2 General settings 178 179=over 180 181=item B<caffhome> [string] 182 183Base directory for the files caff stores. Default: B<$HOME/.caff/>. 184 185=item B<colors> [hash] 186 187How to color output messages. See the C<Term::ANSIColor> documentation 188for the list of supported colors; colored output can be disabled by 189setting this option to an empty hash B<{}>. Default: 190 191 { error => 'bold bright_red' 192 , warn => 'bright_red' 193 , notice => 'bold' 194 , info => '' 195 , success => 'green' # used in combination with 'notice' and 'info' 196 , fail => 'yellow' # used in combination with 'notice' and 'info' 197 } 198 199=back 200 201=head2 GnuPG settings 202 203=over 204 205=item B<gpg> [string] 206 207Path to the GnuPG binary. Default: The value of the I<GNUPGBIN> 208environment variable if set, otherwise C<gpg>. 209 210=item B<secret-keyring> [string] 211 212Path to your secret keyring (GnuPG < 2.1), or to the GnuPGHOME 213of the agent managing the secret key material (GnuPG >= 2.1). 214Default: B<$HOME/.gnupg/secring.gpg>. 215If the value is not a directory with GnuPG >= 2.1, the parent directory 216(i.e., B<$HOME/.gnupg> by default) is considered instead. 217 218=item B<also-encrypt-to> [keyid, or list of keyids] 219 220Additional keyids to encrypt messages to. Default: none. 221 222=item B<gpg-sign-type> [string] 223 224The prefix to the "sign" command used to make the signature from gpg's 225shell. Can be set to a mix of "l" (local), "nr" (non-revocable) or "t" 226(trust) to make a signature of the given type. See gpg(1) for 227details. Default: "" (i.e., make a regular, exportable, signature). 228 229=item B<gpg-sign-args> [string] 230 231Additional commands to pass to gpg after the "sign" command. 232Default: none. 233 234=back 235 236=head2 Key import settings 237 238=over 239 240=item B<no-download> [boolean] 241 242If true, then skip the step of fetching keys from the keyserver. 243Default: B<0>. 244 245=item B<key-files> [list of files] 246 247A list of files containing keys to be imported. 248 249=back 250 251=head2 Signing settings 252 253=over 254 255=item B<no-sign> [boolean] 256 257If true, then skip the signing step. Default: B<0>. 258 259=item B<ask-sign> [boolean] 260 261If true, then pause before continuing to the signing step. 262This is useful for offline signing. Default: B<0>. 263 264=item B<export-sig-age> [seconds] 265 266Don't export UIDs by default, on which your latest signature is older 267than this age. Default: B<24*60*60> (i.e. one day). 268 269=item B<local-user> [keyid, or list of keyids] 270 271Select the key that is used for signing, in case you have more than one key. 272With multiple keyids, sign with each key in turn. 273 274=item B<also-lsign-in-gnupghome> [auto|ask|no] 275 276Whether to locally sign the UIDs in the user's GnuPGHOME, in addition to 277caff's signatures in its own GnuPGHOME. Such signatures are not 278exportable. This can be useful when the recipient forgets to upload the 279signatures caff sent (or if they are non-exportable as well), as it 280gives a way to keep track of which UIDs were verified. However, note 281that local signatures will not be deleted once the recipient does the 282upload and the signer refreshes her keyring. 283 284If the value is not I<no> and if B<gpg-sign-type> contains "l", each 285(local) signature is merely exported from caff's own GnuPGHOME to the 286user's. Otherwise, if the value is I<auto>, each UID signed in caff's 287own GnuPGHOME gets automatically locally signed in the user's, using the 288same certification level; this requires a working gpg-agent(1). If 289I<ask>, the user is prompted for which UIDs to locally sign. Default: 290B<no>. 291 292=item B<show-photos> [boolean] 293 294If true, then before signing a key gpg will display the photos attached 295to it, if any. (The photo viewer can be specified with a "photo-viewer" 296option in caff's GnuPGHOME.) Default: B<0>. 297 298=back 299 300=head2 Mail settings 301 302=over 303 304=item B<mail> [yes|ask-yes|ask-no|no] 305 306Whether to send mails. This is a quad-option, with which you can set the 307behaviour: yes always sends, no never sends; ask-yes and ask-no asks, for 308each uid, with according defaults for the question. Default: B<ask-yes>. 309 310In any case, the messages are also written to $CONFIG{'caffhome'}/keys/ 311 312=item B<mail-cant-encrypt> [yes|ask-yes|ask-no|no] 313 314The value of this option is considered instead of that of B<mail> for 315recipient keys without encryption capability. Default to the value of 316B<mail>. 317 318=item B<mail-subject> [string] 319 320Sets the value of the "Subject:" header field. C<%k> will be expanded 321to the long key ID of the signed key. 322Default: C<Your signed PGP key 0x%k>. 323 324=item B<mail-template> [string] 325 326Email template which is used as the body text for the email sent out 327instead of the default text if specified. The following perl variables 328can be used in the template: 329 330=over 331 332=item B<{owner}> [string] 333 334Your name as specified in the L<B<owner>|/item_owner__5bstring_5d> setting. 335 336=item B<{key}> [string] 337 338The keyid of the key you signed. 339 340=item B<{@uids}> [array] 341 342The UIDs for which signatures are included in the mail. 343 344=back 345 346Note that you should probably customize the template if you intend to 347send non-exportable signatures (i.e., if B<gpg-sign-type> contains "l"), 348as uploading such signatures doesn't make sense, and they require the 349import option "import-local-sigs" which isn't set by default. 350 351=item B<reply-to> [string] 352 353Add a Reply-To: header to messages sent. Default: none. 354 355=item B<bcc> [string] 356 357Address to send blind carbon copies to when sending mail. 358Default: none. 359 360=item B<mailer-send> [array] 361 362Parameters to pass to Mail::Mailer. Default: none. 363Setting this option is strongly discouraged: fix your local MTA instead. 364 365This could for example be 366 367 $CONFIG{'mailer-send'} = [ 'smtp', Server => 'mail.server', Auth => ['user', 'pass'] ]; 368 369to use the perl SMTP client, or 370 371 $CONFIG{'mailer-send'} = [ 'sendmail', '-f', $CONFIG{'email'}, '-it' ]; 372 373to pass arguments to the sendmail program. To specify a sendmail binary 374you can set the C<< PERL_MAILERS >> environment variable as follows: 375 376 $ENV{'PERL_MAILERS'} = 'sendmail:/path/to/sendmail_compatible_mta'; 377 378For more information see Mail::Mailer(3pm). 379 380=back 381 382=head1 NOTES 383 384As noted above caff uses its own GnuPGHOME and GnuPG configuration file. 385In fact it only needs its own keyring for the signing work, but it would 386be unsafe to reuse the same GnuPG configuration file because the user 387could have set an option in $HOME/.gnupg/gpg.conf which would break caff. 388 389Therefore the GnuPG options that are intended to be used with caff, such 390as C<keyserver> or C<cert-digest-algo>, need to be placed in 391$HOME/.caff/gnupghome/gpg.conf instead. If this file does not exist, 392the GnuPG options found in $HOME/.gnupg/gpg.conf that are known to be 393safe (and useful) for caff, are passed to gpg(1) as command-line 394options. 395 396=head1 AUTHORS 397 398=over 399 400=item Peter Palfrader <peter@palfrader.org> 401 402=item Christoph Berg <cb@df7cb.de> 403 404=item Guilhem Moulin <guilhem@debian.org> 405 406=back 407 408=head1 SEE ALSO 409 410gpg(1), pgp-clean(1), /usr/share/doc/signing-party/caff/ 411 412=cut 413 414use strict; 415use warnings; 416use IO::Handle; 417use File::Copy qw{copy}; 418use File::Temp; 419use Text::Template; 420use MIME::Entity; 421use Encode (); 422use I18N::Langinfo qw{langinfo}; 423use Net::IDN::Encode qw{email_to_ascii domain_to_ascii}; 424use Fcntl; 425use IO::Select; 426use Getopt::Long; 427use GnuPG::Interface; 428use POSIX qw{strftime setlocale}; 429use Term::ANSIColor qw{colored}; 430 431my %CONFIG; 432my $VERSION = '@@VERSION@@'; 433my $LOCALE = Encode::find_encoding(langinfo(I18N::Langinfo::CODESET())); 434my $USER_AGENT = "caff $VERSION"; 435 436# Global variables 437my @KEYIDS; 438my @LOCAL_USER; 439my $PARAMS; 440 441my $KEYSBASE; 442my $GNUPGHOME; 443 444 445## 446# Display an error message on STDERR and then exit. 447# 448# @param $exitcode exit code status to use to end the program 449# @param $line error message to display on STDERR 450# 451sub mycolored($@) { 452 my $msg = shift; 453 my $color = join (' ', grep defined, map { defined $_ ? $CONFIG{colors}->{$_} : undef } @_) if defined $CONFIG{colors}; 454 $msg = colored($msg, $color) if defined $color and $color !~ /^\s*$/; 455 return $msg; 456} 457sub myerror($$) { 458 my ($exitcode, $line) = @_; 459 print STDERR mycolored("[ERROR] $line", 'error'), "\n"; 460 exit $exitcode; 461} 462 463sub mywarn($) { 464 my ($line) = @_; 465 print STDERR mycolored("[WARN] $line", 'warn'), "\n"; 466} 467sub notice($;$) { 468 my ($line,$color) = @_; 469 $color = $color ? 'success' : 'fail' if defined $color; 470 print STDERR mycolored("[NOTICE] $line", 'notice', $color), "\n"; 471} 472sub info($;$) { 473 my ($line,$color) = @_; 474 $color = $color ? 'success' : 'fail' if defined $color; 475 print STDERR mycolored("[INFO] $line", 'info', $color), "\n"; 476} 477sub debug($) { 478 my ($line) = @_; 479 print STDERR "[DEBUG] $line\n" if $PARAMS->{debug}; 480} 481sub trace($) { 482 my ($line) = @_; 483 #print STDERR "[trace] $line\n"; 484} 485sub trace2($) { 486 my ($line) = @_; 487 #print STDERR "[trace2] $line\n"; 488} 489 490sub mysystem(@) { 491 system { $_[0] } @_; 492 myerror($?, "$_[0] exited with value ".($? >> 8)) if $?; 493} 494 495 496# Return -1 if the GnuPG version is < $_[0], 0 if == $_[0], 1 if > $_[0]. 497my $GNUPG_VERSION; 498sub GnuPG_version($) { 499 unless (defined $GNUPG_VERSION) { # cache the version 500 $GNUPG_VERSION = `$CONFIG{gpg} --no-options --with-colons --list-config version` or exit 1; 501 chomp $GNUPG_VERSION; 502 $GNUPG_VERSION =~ s/^cfg:version:// or die; 503 debug "gpg (GnuPG) $GNUPG_VERSION"; 504 } 505 my @v1 = split /\./, $GNUPG_VERSION; 506 my @v2 = split /\./, shift; 507 while (@v1 or @v2) { 508 my $v1 = shift @v1 // 0; 509 my $v2 = shift @v2 // 0; 510 my $r = $v1 <=> $v2; 511 return $r unless $r == 0; 512 } 513 return 0; 514} 515 516sub gpgconf(@) { 517 my $pid = open my $fh, '-|', 'gpgconf', @_; 518 my %conf; 519 while (<$fh>) { 520 my ($k, $v) = split /:/, $_; 521 chomp ($conf{$k} = $v); 522 $conf{$k} =~ s/%(\p{AHex}{2})/ chr(hex($1)) /ge; # unescape the %-encoded chars 523 } 524 waitpid $pid, 0; 525 myerror($?, "gpgconf exited with value ".($? >> 8)) if $? > 0; 526 close $fh; 527 return \%conf; 528} 529 530# See RFC 5322 section 3.4.1; only the pattern for the local part, which 531# doesn't go beyond the ASCII range, is validated. The domain part is 532# NOT checked against RFC 5322, as it must be encoded to ASCII first; 533# for now any string in the full-range unicode that does not contain 534# U+0040 (commercial at), U+FE6B (small commercial at) and U+FF20 535# (fullwidth commercial at) is accepted. 536my $RE_word = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+ # atom: any ASCII CHAR except specials, SPACE and CTLs 537 |\x22(?:[\x00-\x21\x23-\x5B\x5D-\x7E]|\x5C\p{ASCII})*\x22 # quoted string 538 /x; 539my $RE_address_spec = qr/(?<l>$RE_word(?:\.$RE_word)*)[\@\N{U+FE6B}\N{U+FF20}](?<d>[^\@\N{U+FE6B}\N{U+FF20}]+)/o; 540 541# A domain label is a non-empty ASCII string of length at most 63 542# characters (RFC 1035 2.3.4). Valid characters are alphanumeric and 543# hyphen '-', but an hyphen may not appear at the start or end of a 544# label (RFC 952, RFC 1123 2.1). 545my $RE_label = qr/[0-9a-z](?:[0-9a-z\x2D]{0,61}[0-9a-z])?/aai; 546 547# Take a 'mailbox' (RFC 5322 section 3.4) and return its ASCII-encoded 548# 'addr-spec'; or undef if it violates one of RFC 5322/5892/1035/5321. 549# We're not using Email::Valid because it's not unicode-friendly. 550# NOTE: This subroutine should only be used to extract e-mail addresses 551# from UIDs. The phrase is NOT checked against RFC 5322 (any string 552# containing only characters in the full-unicode printable range are 553# accepted), but we don't care as long as it's not used in email 554# headers. 555sub email_valid($) { 556 local $_ = shift // return; 557 return unless /\A$RE_address_spec\z/ao or # addr-spec 558 /\A(?:\p{Print}*\p{Space})?<$RE_address_spec>\z/ao; # [phrase] "<" addr-spec ">" 559 my ($l,$d) = @+{qw/l d/}; 560 if ($d =~ /\P{ASCII}/) { 561 # encode the IDN to ASCII using Punycode for RFC 5321 validation 562 eval { $d = domain_to_ascii($d) }; 563 return if $@; # violates RFC 5892 564 } 565 my $address = "$l\@$d"; 566 return unless 567 length $d > 0 and length $d <= 255 # violates RFC 1035 2.3.4 "size limits" 568 and length $l <= 64 # violates RFC 5321 4.5.3.1.1 569 and length $address <= 254 # violates RFC 5321 4.5.3.1.3 570 and $d =~ /\A$RE_label(?:\.$RE_label)+\z/o; # ignore non-FQDN 571 return $address; 572} 573 574open NULL, '+<', '/dev/null'; 575my $NULL = fileno NULL; 576sub generate_config() { 577 notice("Error: \$LOGNAME is not set", 0) unless defined $ENV{'LOGNAME'}; 578 my $gecos = defined $ENV{'LOGNAME'} ? (getpwnam($ENV{LOGNAME}))[6] : undef; 579 my $email; 580 my @keys; 581 # BSD does not have hostname -f, so we try without -f first 582 my $hostname = `hostname`; 583 $hostname = `hostname -f` unless $hostname =~ /\./; 584 chomp $hostname; 585 my ($Cgecos,$Cemail,$Ckeys) = ('','',''); 586 587 if (defined $gecos) { 588 $gecos =~ s/,.*//; 589 590 $CONFIG{'gpg'} = $ENV{GNUPGBIN} // 'gpg'; 591 my $gpg = mkGnuPG( extra_args => ['--with-colons'] ); 592 my $handles = mkGnuPG_fds ( stdout => undef ); 593 my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $gecos ]); 594 my %output = readwrite_gpg($handles); 595 waitpid $pid, 0; 596 $handles->{stdout}->close; 597 598 if ($output{stdout} eq '') { 599 mywarn "No data from $CONFIG{gpg} for list-key"; # There should be at least 'tru:' everywhere. 600 }; 601 602 @keys = ($output{stdout} =~ /^pub:[^eir:]*:(?:[^:]*:){2}([0-9A-F]{16}):/mg); 603 unless (scalar @keys) { 604 notice("Error: No keys were found using \"$CONFIG{gpg} --list-public-keys '$gecos'\"", 0); 605 @keys = qw{0123456789ABCDEF 89ABCDEF76543210}; 606 $Ckeys = '#'; 607 } 608 my @emails = ($output{stdout} =~ /^uid:[^eir:]*:(?:[^:]*:){7}([^:]+)(?::.*)?$/mg); 609 if (@emails) { 610 s/\\x(\p{AHex}{2})/ chr(hex($1)) /ge foreach @emails; 611 @emails = grep defined, map {email_valid(Encode::decode_utf8($_))} @emails; 612 $email = shift @emails; # take the first valid address 613 } 614 unless (defined $email) { 615 notice("Error: No email address was found using \"$CONFIG{gpg} --list-public-keys '$gecos'\"", 0); 616 $email = $ENV{'LOGNAME'}.'@'.$hostname; 617 $Cemail = '#'; 618 } 619 } else { 620 $gecos = 'Unknown Caff User'; 621 $email = $ENV{'LOGNAME'}.'@'.$hostname; 622 @keys = qw{0123456789ABCDEF 89ABCDEF76543210}; 623 ($Cgecos,$Cemail,$Ckeys) = ('#','#','#'); 624 }; 625 626 my $template = <<EOT; 627# .caffrc -- vim:ft=perl: 628# This file is in perl(1) format - see caff(1) for details. 629 630$Cgecos\$CONFIG{'owner'} = '$gecos'; 631$Cemail\$CONFIG{'email'} = '$email'; 632#\$CONFIG{'reply-to'} = 'foo\@bla.org'; 633 634# You can get your long keyid from 635# $CONFIG{gpg} --keyid-format long --list-key <yourkeyid|name|emailaddress..> 636# 637# If you have a v4 key, it will simply be the last 16 digits of 638# your fingerprint. 639# 640# Example: 641# \$CONFIG{'keyid'} = [ qw{FEDCBA9876543210} ]; 642# or, if you have more than one key: 643# \$CONFIG{'keyid'} = [ qw{0123456789ABCDEF 89ABCDEF76543210} ]; 644$Ckeys\$CONFIG{'keyid'} = [ qw{@keys} ]; 645 646# Select this/these keys to sign with 647#\$CONFIG{'local-user'} = [ qw{@keys} ]; 648 649# Additionally encrypt messages for these keyids 650#\$CONFIG{'also-encrypt-to'} = [ qw{@keys} ]; 651 652# Mail template to use for the encrypted part 653#\$CONFIG{'mail-template'} = << 'EOM'; 654EOT 655 656 $template .= "#$_" foreach <DATA>; 657 $template .= "#EOM\n"; 658 return $template; 659} 660 661my @GNUPGOPTS; 662sub load_config() { 663 my $config = $ENV{'HOME'} . '/.caffrc'; 664 unless (-f $config) { 665 notice "No configfile $config present, I will use this template:"; 666 my $template = generate_config(); 667 print $template, "\n"; 668 notice "Please edit $config and run caff again."; 669 open F, '>', $config or myerror(1, "$config: $!"); 670 print F $template; 671 close F; 672 exit(1); 673 } 674 unless (scalar eval `cat $config`) { 675 myerror(1, "Couldn't parse $config: $@") if $@; 676 }; 677 678 myerror(1, "$0: $_ is not defined in $config") for grep {!defined $CONFIG{$_}} qw/owner email keyid/; 679 myerror(1, "$0: keyid is not an array ref in $config") unless ref $CONFIG{'keyid'} eq 'ARRAY'; 680 myerror(1, "$0: key $_ is not specified as a long (16 digit) keyid or fingerprint in $config") for 681 grep !/^((?:0x)?\p{AHex}{16}|\p{AHex}{40}|(?:\p{AHex}{4} ){5}(?: \p{AHex}{4}){5})$/, @{$CONFIG{'keyid'}}; 682 683 $CONFIG{'caffhome'} //= $ENV{'HOME'}.'/.caff'; 684 $KEYSBASE = $CONFIG{'caffhome'}.'/keys'; 685 $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome'; 686 foreach ($CONFIG{'caffhome'}, $KEYSBASE, $GNUPGHOME) { 687 next if -d $_; 688 debug("Creating $_"); 689 mkdir $_, 0700 or myerror(1, "Cannot mkdir $_: $!"); 690 } 691 692 @{$CONFIG{'keyid'}} = map { s/^0x//; uc (substr y/ //dr, -16) } @{$CONFIG{'keyid'}}; # must be a list of long keyids 693 $CONFIG{'export-sig-age'} //= 24*60*60; 694 $CONFIG{'gpg'} //= $ENV{GNUPGBIN} // 'gpg'; 695 696 $CONFIG{'secret-keyring'} //= ($ENV{'GNUPGHOME'} // "$ENV{'HOME'}/.gnupg") . '/secring.gpg'; 697 $CONFIG{'no-download'} //= 0; 698 $CONFIG{'no-sign'} //= 0; 699 $CONFIG{'key-files'} //= []; 700 $CONFIG{'mailer-send'} //= []; 701 myerror(1, "$0: mailer-send is not an array ref in $config") unless ref $CONFIG{'mailer-send'} eq 'ARRAY'; 702 $CONFIG{'mail-subject'} //= "Your signed PGP key 0x%k"; 703 $CONFIG{'mail-template'} //= do { local $/; <DATA> }; 704 $CONFIG{'also-encrypt-to'} = [ $CONFIG{'also-encrypt-to'} ] 705 if defined $CONFIG{'also-encrypt-to'} and !ref $CONFIG{'also-encrypt-to'}; 706 if (defined $LOCALE) { 707 $CONFIG{$_} = $LOCALE->decode($CONFIG{$_}) for qw/owner mail-template mail-subject/; 708 $CONFIG{$_} = email_to_ascii($LOCALE->decode($CONFIG{$_})) 709 for grep {defined $CONFIG{$_}} qw/email bcc reply-to/; 710 } 711 $CONFIG{'gpg-sign-type'} //= ''; 712 myerror(1, "$0: $CONFIG{'gpg-sign-type'} is an invalid signature type") 713 unless $CONFIG{'gpg-sign-type'} =~ /^(?:l|nr|t)*$/; 714 $CONFIG{'also-lsign-in-gnupghome'} //= 'no'; 715 $CONFIG{'also-lsign-in-gnupghome'} = 'no' if $CONFIG{'no-sign'}; 716 myerror(1, "$0: invalid value for 'also-lsign-in-gnupghome': $CONFIG{'also-lsign-in-gnupghome'}") 717 unless grep { $_ eq $CONFIG{'also-lsign-in-gnupghome'} } qw/auto ask no/; 718 $CONFIG{'show-photos'} //= 0; 719 720 $CONFIG{colors} //= { 721 error => 'bold bright_red', 722 warn => 'bright_red', 723 notice => 'bold', 724 info => '', 725 success => 'green', 726 fail => 'yellow' 727 }; 728 729 # Import some options from ~/.gnupghome/gpg.conf. We don't symlink 730 # the whole file because the user could have set an option in 731 # ~/.gnupg/gpg.conf which would break caff. 732 my $gpgconf = ($ENV{'GNUPGHOME'} // "$ENV{'HOME'}/.gnupg") . '/gpg.conf'; 733 if ( ! -f "$GNUPGHOME/gpg.conf" and -f $gpgconf and open my $fh, '<', $gpgconf) { 734 # the list of options to import from ~/.gnupg/gpg.conf; only 735 # safe (and useful) options for caff should be listed here 736 my @gnupgopts_n = qw/ 737 disable-ccid 738 ask-cert-level no-ask-cert-level 739 use-agent no-use-agent 740 no-random-seed-file 741 no-greeting 742 expert no-expert 743 gnupg no-pgp2 pgp6 no-pgp6 pgp7 no-pgp7 pgp8 no-pgp8 744 rfc2440 rfc4880 openpgp 745 default-comment no-comments 746 emit-version no-emit-version no-version 747 allow-weak-digest-algos 748 ask-cert-expire no-ask-cert-expire 749 utf8-strings no-utf8-strings 750 only-sign-text-ids 751 /; 752 my @gnupgopts_i = qw/ 753 default-cert-level 754 limit-card-insert-tries 755 /; 756 my @gnupgopts_s = qw/ 757 photo-viewer 758 exec-path 759 pcsc-driver 760 reader-port 761 display-charset charset 762 keyserver keyserver-options 763 gpg-agent-info 764 personal-cipher-preferences personal-digest-preferences 765 comment 766 cert-notation set-notation 767 cert-policy-url set-policy-url 768 cipher-algo cert-digest-algo 769 pinentry-mode 770 weak-digest 771 default-cert-expire 772 disable-cipher-algo disable-pubkey-algo 773 agent-program dirmngr-program 774 /; 775 776 push @GNUPGOPTS, '--no-options'; 777 notice('Importing GnuPG options from '.($ENV{'GNUPGHOME'} // '~/.gnupg').'/gpg.conf:'); 778 while (<$fh>) { 779 s/(?:\r\n|\r|\n)\z// or next; 780 if (/\A\s*([0-9a-zA-Z\-]+)\z/ and grep { $_ eq $1 } @gnupgopts_n) { 781 push @GNUPGOPTS, "--$1"; 782 } elsif (/\A\s*([0-9a-zA-Z\-]+)\s+(\d+)\z/ and grep { $_ eq $1 } @gnupgopts_i) { 783 push @GNUPGOPTS, "--$1=$2"; 784 } elsif (/\A\s*([0-9a-zA-Z\-]+)\s+(\P{Control}+)\z/ and grep { $_ eq $1 } @gnupgopts_s) { 785 push @GNUPGOPTS, "--$1=$2"; 786 } else { 787 next; 788 } 789 notice(" $_"); 790 } 791 close $fh; 792 } 793 794 # deprecated options, will be removed in a future release 795 mywarn("Deprecated option \$CONFIG{'$_'} = '$CONFIG{$_}'") foreach 796 grep {defined $CONFIG{$_}} qw{gpg-sign gpg-delsig keyserver}; 797} 798 799# Create a new GnuPG::Interface object with common options 800sub mkGnuPG(%) { 801 my %h = @_; 802 my $gpg = GnuPG::Interface::->new(); 803 $gpg->call( $CONFIG{'gpg'} ); 804 805 $h{meta_interactive} //= 0; 806 $h{always_trust} //= 1; 807 $h{extra_args} //= []; 808 809 unshift @{$h{extra_args}}, '--no-auto-check-trustdb'; 810 unshift @{$h{extra_args}}, '--fixed-list-mode' if GnuPG_version('2.0.0') < 0; 811 unshift @{$h{extra_args}}, '--no-autostart' if GnuPG_version('2.1.0') >= 0; # never autostart 812 unshift @{$h{extra_args}}, @GNUPGOPTS if @GNUPGOPTS and defined $h{homedir}; 813 814 $gpg->options->hash_init(%h); 815 debug(join (' ', $gpg->call(), $gpg->options->get_args(), "...")); 816 return $gpg; 817} 818 819 820# Create a GnuPG::Handles object. This function takes a hash where keys 821# are handle names, and values are either IO::Handle objects, in which 822# case the existing handle is used, or undefined, in which case a new 823# IO::Handle is created. 824sub mkGnuPG_fds(%) { 825 my %fd = @_; 826 my @direct; 827 828 foreach (keys %fd) { 829 push @direct, $_ if defined $fd{$_} and $fd{$_} !~ /^[<>]&/; 830 $fd{$_} //= IO::Handle::->new(); 831 } 832 833 # Redirect the STDIN and STDOUT to /dev/null unless explicitly 834 # redirected. Also redirect logger to /dev/null in non-debug mode, 835 # but NEVER redirect STDERR! 836 $fd{stdin} = "<&=$NULL" unless exists $fd{stdin}; 837 $fd{stdout} = ">&=$NULL" unless exists $fd{stdout}; 838 $fd{logger} = ">&=$NULL" unless exists $fd{logger} or $PARAMS->{debug}; 839 840 my $handles = GnuPG::Handles::->new(%fd); 841 $handles->options($_)->{direct} = 1 foreach @direct; 842 debug(join (', ', map {"$_: " . ($handles->options($_)->{direct} ? $fd{$_}->fileno : $fd{$_})} keys %fd)); 843 844 return $handles; 845} 846 847sub done_gpg($;$) { 848 my ($pid, $handles) = @_; 849 waitpid $pid, 0; 850 mywarn("$CONFIG{gpg} exited with value ".($? >> 8)) if $? > 0; 851 return unless defined $handles; 852 foreach (GnuPG::Handles::HANDLES) { 853 next unless defined $handles->{$_} and $handles->{$_} !~ /^[<>]&/; 854 $handles->{$_}->close if $handles->{$_}->opened; 855 } 856} 857 858 859# Send some data on GnuPG handles, and retrieve output from all handles 860# at once using select(2) syscalls. Stop when some output matches a 861# given regex, or when there nothing more to read or write. A newline 862# '\n' character is automatically appended to the text to be send to the 863# 'command' handle; the prefix "[GNUPG:] " to the 'status' handle is 864# added as well. 865sub readwrite_gpg($%) { 866 my $handles = shift; 867 my %opts = @_; 868 869 # ignore direct and dup handles 870 my @infhs = grep {defined $opts{$_} and !$handles->options($_)->{direct} and $handles->{$_} !~ /^[<>]&/} qw/stdin passphrase command/; 871 my @outfhs = grep {defined $handles->{$_} and !$handles->options($_)->{direct} and $handles->{$_} !~ /^[<>]&/} qw/stdout stderr status logger/; 872 my %fh = map { $handles->{$_} => $_ } (@infhs, @outfhs); 873 874 my %offset = map {$_ => 0} @infhs; 875 my %output = map {$_ => ''} @outfhs; 876 877 if (defined $opts{command}) { 878 # automatically send the command 879 chomp $opts{command}; 880 $opts{command} .= "\n"; 881 } 882 $opts{status} = qr/^\[GNUPG:\] $opts{status}$/m if defined $opts{status}; 883 884 $handles->{$_}->blocking(0) foreach (@infhs, @outfhs); 885 my $sin = IO::Select::->new(map {$handles->{$_}} @infhs); 886 my $sout = IO::Select::->new(map {$handles->{$_}} @outfhs); 887 888 trace("entering readwrite_gpg."); 889 trace("doing stuff until one of: ". join(', ', map {"$_ =~ $opts{$_}"} grep {defined $opts{$_}} @outfhs)) 890 if grep {defined $opts{$_}} @outfhs; 891 892 my $readwrote_stuff_this_time = 0; 893 my $do_not_wait_on_select = 0; 894 while ($sin->count() + $sout->count() > 0) { 895 if (!$sin->count() and grep {defined $opts{$_} and $output{$_} =~ $opts{$_}} @outfhs) { 896 if ($readwrote_stuff_this_time) { 897 trace("read/write some more."); 898 $do_not_wait_on_select = 1; 899 } else { 900 trace("that's it in our while loop."); 901 last; 902 } 903 }; 904 905 trace("select waiting for ".($sin->count()+$sout->count())." fds."); 906 my ($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, $do_not_wait_on_select ? 0 : 1); 907 trace("ready: write: ". join (',', map {$fh{$_}} @{$readyw // []}). 908 "; read: ". join (',', map {$fh{$_}} @{$readyr // []})); 909 $readwrote_stuff_this_time = 0; 910 911 for my $fd (@{$readyw // []}) { 912 $readwrote_stuff_this_time = 1; 913 my $fh = $fh{$fd}; 914 if ($offset{$fh} != length $opts{$fh}) { 915 trace ("writing to '$fh'". ($offset{$fh} ? "" : ": ".(split /\n/, $opts{$fh}, 2)[0])); 916 my $written = $fd->syswrite($opts{$fh}, length($opts{$fh}) - $offset{$fh}, $offset{$fh}); 917 $offset{$fh} += $written; 918 } 919 if ($offset{$fh} == length $opts{$fh}) { 920 trace "done writing to '$fh'."; 921 $sin->remove($fd); 922 $fd->close && trace "closed '$fh'." if $opts{autoclose}; 923 } 924 } 925 for my $fd (@{$readyr // []}) { 926 $readwrote_stuff_this_time = 1; 927 my $fh = $fh{$fd}; 928 if ($fd->eof) { 929 trace "done reading from '$fh'."; 930 $sout->remove($fd); 931 next; 932 } 933 trace "reading from '$fh'."; 934 $output{$fh} .= do { local $/; <$fd> }; 935 trace2 "$fh is now:\n$output{$fh}\n================"; 936 } 937 } 938 trace("readwrite_gpg done."); 939 return %output; 940} 941 942 943sub ask($$;$$) { 944 my ($question, $default, $forceyes, $forceno) = @_; 945 946 my $fd = fileno(TTY) // die; 947 my $termios = POSIX::Termios::->new(); 948 $termios->getattr($fd) // die "getattr: $!"; 949 my $echo = POSIX::ECHO | POSIX::ECHOK | POSIX::ICANON; 950 my $c_iflag = $termios->getiflag(); 951 my $c_lflag = $termios->getlflag(); 952 unless ($c_iflag & POSIX::ICRNL and ($c_lflag & $echo) == $echo) { 953 debug("Sanitizing TTY"); 954 $termios->setiflag( $c_iflag | POSIX::ICRNL ); 955 $termios->setlflag( $c_lflag | $echo ); 956 $termios->setattr($fd, POSIX::TCSANOW) // warn "setattr: $!"; 957 } 958 959 my $answer; 960 my $yn = $default ? '[Y/n]' : '[y/N]'; 961 while (1) { 962 print $question,' ',$yn, ' '; 963 if ($forceyes && $forceno) { 964 print "$default (from config/command line)\n"; 965 return $default; 966 }; 967 if ($forceyes) { 968 print "YES (from config/command line)\n"; 969 return 1; 970 }; 971 if ($forceno) { 972 print "NO (from config/command line)\n"; 973 return 0; 974 }; 975 976 $answer = <TTY>; 977 chomp $answer; 978 last if ((length $answer == 0) || ($answer =~ m/^[yYnN]$/) ); 979 print "What about $yn is so hard to understand?\nAnswer with either 'n' or 'y' or just press enter for the default.\n"; 980 sleep 1; 981 }; 982 my $result = $default; 983 $result = 1 if $answer =~ /y/i; 984 $result = 0 if $answer =~ /n/i; 985 return $result; 986} 987 988 989 990 991 992my $KEYEDIT_PROMPT = qr/GET_LINE keyedit\.prompt/; 993my $KEYEDIT_DELUID_PROMPT = qr/GET_BOOL keyedit\.remove\.uid\.okay/; 994my $KEYEDIT_DELSIG_PROMPT = qr/GET_BOOL keyedit\.delsig\.(?:unknown|invalid|valid)/; # we won't delete selfsigs 995my $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT = qr/$KEYEDIT_PROMPT|$KEYEDIT_DELSIG_PROMPT/; 996my $KEYEDIT_DELSUBKEY_PROMPT = qr/GET_BOOL keyedit\.remove\.subkey\.okay/; 997my $KEYEDIT_SIGNUID_CLASS_PROMPT = qr/GET_LINE sign_uid\.class/; 998my $KEYEDIT_SIGNUID_PROMPT = qr/GET_BOOL sign_uid\.okay/; 999 1000 1001sub version($) { 1002 my ($fd) = @_; 1003 print $fd "$0 $VERSION - (c) 2004, 2005, 2006 Peter Palfrader et al.\n"; 1004} 1005 1006sub usage($$) { 1007 my ($fd, $exitcode) = @_; 1008 version($fd); 1009 print $fd "Usage: $0 [-eERS] [-m <yes|ask-yes|ask-no|no>] [-u <yourkeyid>] <keyid> [<keyid> ...]\n"; 1010 print $fd "Consult the manual page for more information.\n"; 1011 exit $exitcode; 1012} 1013 1014###### 1015# export keys @$keyids from $gnupghome. In list context, fork and 1016# return the pid and the file descriptor of its standard output; 1017# otherwise, wait until the export is done and return the ASCII key. 1018# 1019# /!\ Failure to export a key will not be detected, unless *all* keys 1020# couldn't be exported. Therefore for safe export/import, you need 1021# to inspect '$asciikey' or the status FD on the import side. 1022###### 1023sub export_keys($$@) { 1024 my ($gnupghome, $keyids, @export_options) = @_; 1025 myerror(1, "Nothing to export") unless defined $keyids and @$keyids; 1026 my @extra_args = ('--export-options', join (',', @export_options)) if @export_options; 1027 1028 # don't armor when piping since it's faster 1029 my $gpg = mkGnuPG( homedir => $gnupghome, armor => (wantarray ? 0 : 1), extra_args => \@extra_args ); 1030 my $handles = mkGnuPG_fds( stdout => undef ); 1031 my $pid = $gpg->export_keys( handles => $handles, command_args => $keyids ); 1032 1033 if (wantarray) { 1034 return ($pid, $handles->{stdout}); 1035 } else { 1036 my $asciikey = do { local $/; readline $handles->{stdout} }; 1037 done_gpg($pid, $handles); 1038 return $asciikey; 1039 } 1040} 1041 1042 1043###### 1044# Create an email to $address. If $can_encrypt is true then the mail 1045# will be PGP/MIME encrypted to $longkeyid. 1046# 1047# $longkeyid, $uid, and @attached will be used in the email and the template. 1048###### 1049# create_mail($address, $can_encrypt, $longkeyid, $uid, @attached); 1050sub create_mail($$$@) { 1051 my ($address, $can_encrypt, $key_id, @keys) = @_; 1052 1053 my $template = Text::Template::->new(TYPE => 'STRING', SOURCE => $CONFIG{'mail-template'}); 1054 myerror(1, "Cannot create template: $Text::Template::ERROR") unless defined $template; 1055 1056 my $message = $template->fill_in(HASH => { key => $key_id, 1057 uids => [ map {$_->{'text'}} @keys ], 1058 owner => $CONFIG{'owner'}}); 1059 myerror(1, "Cannot fill in template: $Text::Template::ERROR") unless defined $message; 1060 1061 my $message_entity = MIME::Entity->build( 1062 Type => "text/plain", 1063 Charset => "utf-8", 1064 Disposition => 'inline', 1065 Data => Encode::encode_utf8($message)); 1066 1067 my @key_entities; 1068 for my $key (@keys) { 1069 $message_entity->attach( 1070 Type => "application/pgp-keys", 1071 Disposition => 'attachment', 1072 Encoding => "7bit", 1073 Description => "PGP Key 0x$key_id, uid ".Encode::encode_utf8($key->{text})." ($key->{serial}), signed by 0x$CONFIG{keyid}[0]", 1074 Data => $key->{key}, 1075 Filename => "0x$key_id.$key->{serial}.signed-by-0x$CONFIG{keyid}[0].asc"); 1076 }; 1077 1078 if ($can_encrypt) { 1079 my $gpg = mkGnuPG( homedir => $GNUPGHOME, armor => 1, textmode => 1 ); 1080 $gpg->options->push_recipients($key_id); 1081 $gpg->options->push_recipients(@{$CONFIG{'also-encrypt-to'}}) if defined $CONFIG{'also-encrypt-to'}; 1082 my $handles = mkGnuPG_fds( stdin => undef, stdout => undef, status => undef ); 1083 my $pid = $gpg->encrypt(handles => $handles); 1084 my %output = readwrite_gpg($handles, stdin => $message_entity->stringify(), autoclose => 1); 1085 done_gpg($pid, $handles); 1086 my ($message, $status) = @output{qw/stdout status/}; 1087 1088 if ($message eq '') { 1089 if ($status =~ /^\[GNUPG:\] INV_RECP ([0-9]+) ([0-9A-F]+)$/m and defined $CONFIG{'also-encrypt-to'}) { 1090 my $reason = $1; 1091 my $keyid = $2; 1092 if (grep { $_ eq $keyid } @{$CONFIG{'also-encrypt-to'}}) { 1093 mywarn "Could not encrypt to $keyid, specified in CONFIG{'also-encrypt-to'}"; 1094 mywarn "Try to update the key using gpg --homedir=$GNUPGHOME --import <exported key>"; 1095 return; 1096 }; 1097 }; 1098 mywarn "No data from gpg for encrypting mail; status output was:\n$status"; 1099 return; 1100 }; 1101 1102 $message_entity = MIME::Entity->build( 1103 Type => 'multipart/encrypted; protocol="application/pgp-encrypted"', 1104 Encoding => '7bit' ); 1105 1106 $message_entity->attach( 1107 Type => "application/pgp-encrypted", 1108 Filename => "signedkey.msg", 1109 Disposition => 'attachment', 1110 Encoding => "7bit", 1111 Data => "Version: 1\n" ); 1112 1113 $message_entity->attach( 1114 Type => "application/octet-stream", 1115 Filename => 'msg.asc', 1116 Disposition => 'inline', 1117 Encoding => "7bit", 1118 Data => $message ); 1119 }; 1120 1121 my $from = Encode::encode('MIME-Q', $CONFIG{owner})." <$CONFIG{email}>"; 1122 $message_entity->head->add("From", $from); 1123 $message_entity->head->add("Date", strfCtime("%a, %e %b %Y %H:%M:%S %z", localtime)); 1124 $message_entity->head->add("Subject", Encode::encode('MIME-Q', $CONFIG{'mail-subject'} =~ s/%k/$key_id/gr)); 1125 $message_entity->head->add("To", $address); 1126 $message_entity->head->add("Sender", $from); 1127 $message_entity->head->add("Reply-To", $CONFIG{'reply-to'}) if defined $CONFIG{'reply-to'}; 1128 $message_entity->head->add("Bcc", $CONFIG{'bcc'}) if defined $CONFIG{'bcc'}; 1129 $message_entity->head->add("User-Agent", $USER_AGENT); 1130 return $message_entity; 1131} 1132 1133###### 1134# send a mail message (MIME::Entity) 1135###### 1136my $warned_about_broken_mailer_send = 0; 1137sub send_message($) { 1138 my ($message_entity) = @_; 1139 1140 if ((scalar @{$CONFIG{'mailer-send'}} > 0) && !$warned_about_broken_mailer_send) { 1141 mywarn("You have set arguments to pass to Mail::Mailer. Better fix your MTA. (Also, Mail::Mailer's error reporting is non existent, so it won't tell you when it doesn't work.)"); 1142 $warned_about_broken_mailer_send = 1; 1143 }; 1144 $message_entity->send(@{$CONFIG{'mailer-send'}}); 1145} 1146 1147###### 1148# clean up a UID so that it can be used on the FS. 1149###### 1150sub sanitize_uid($) { 1151 my ($uid) = @_; 1152 1153 my $good_uid = $uid; 1154 $good_uid =~ tr#/:\\#_#; 1155 trace2("[sanitize_uid] changed UID from $uid to $good_uid.\n") if $good_uid ne $uid; 1156 return $good_uid; 1157} 1158 1159# Delete all non self-sigs that are not made by one of the @$keyids, and 1160# return the date of the most recent signature and a hash reference 1161# {$signer => $level} of the keys in @$keyids that have an exportable 1162# signature on that $uid. If $keep_lsigs_only, our exportable 1163# signatures are removed as well. 1164sub delete_signatures($$$$;$) { 1165 my ($handles, $longkeyid, $uid, $keyids, $keep_lsigs_only) = @_; 1166 1167 readwrite_gpg($handles, command => "uid 0", status => $KEYEDIT_PROMPT); # unmark all uids from delsig 1168 readwrite_gpg($handles, command => "uid $uid", status => $KEYEDIT_PROMPT); # mark $uid for delsig 1169 1170 my $last_signed_on = 0; 1171 my %xsigners; 1172 1173 my %output = readwrite_gpg($handles, command => "delsig", status => $KEYEDIT_DELSIG_PROMPT); 1174 1175 while($output{status} =~ /$KEYEDIT_DELSIG_PROMPT/m) { 1176 # sig:?::17:EA2199412477CAF8:1058095214:::::13x 1177 my @sigline = grep /^sig:/, (split /\n/, $output{stdout}); 1178 my $answer = "no"; 1179 if (!@sigline) { 1180 debug("[sigremoval] no sig line here, only got:\n".$output{stdout}); 1181 } 1182 else { # only if we found a sig here - we never remove revocation packets for instance 1183 my $sig = pop @sigline; 1184 $sig =~ /^sig:(?:[^:]*:){3}([0-9A-F]{16}):(\d+):(?:[^:]*:){4}(1[0-3]|30)([lx])(?::.*)?$/ or 1185 mywarn("I hit a bug, please report: Couldn't parse sigline $sig"); 1186 debug("[sigremoval] doing sigline $sig"); 1187 if ($1 eq $longkeyid) { 1188 debug("[sigremoval] selfsig ($1)"); 1189 $answer = "no"; 1190 } elsif (grep { $1 eq $_ } @$keyids and $3 != 30) { 1191 debug("[sigremoval] signed by us ($1)"); 1192 $answer = ($keep_lsigs_only and $4 eq 'x') ? "yes" : "no"; 1193 $last_signed_on = $2 if $last_signed_on < $2; 1194 $xsigners{$1} = $3-10 if $4 eq 'x'; 1195 } else { 1196 debug("[sigremoval] not interested in that sig ($1)"); 1197 $answer = "yes"; 1198 }; 1199 mywarn("I hit a bug, please report: Found the following ".($#sigline+2)." siglines in that part of the dialog:\n".$output{stdout}) if @sigline; 1200 } 1201 %output = readwrite_gpg($handles, command => $answer, status => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT); 1202 } 1203 1204 return ($last_signed_on, \%xsigners); 1205} 1206 1207## 1208# Check the local user keys. 1209# 1210# This function checks if the keyids defined through the --local-user 1211# command line option or set in ~/.caffrc are valid and known to be one of the 1212# keyids listed in ~/.caffrc. 1213# 1214# @return an array containing the local user keys\n 1215# (undef) if no valid key has been found 1216# 1217sub get_local_user_keys() { 1218 # No user-defined key id has been specified by the user, no need for 1219 # further checks 1220 return @{$CONFIG{'keyid'}} unless $CONFIG{'local-user'}; 1221 1222 # Parse the list of keys 1223 my @key_list = ref $CONFIG{'local-user'} ? @{$CONFIG{'local-user'}} : split /\s*,\s*/, $CONFIG{'local-user'}; 1224 my @local_user; 1225 1226 # Check every key defined by the user... 1227 for my $user_key (@key_list) { 1228 unless ($user_key =~ m/^((?:0x)?\p{AHex}{8}|(?:0x)?\p{AHex}{16}|\p{AHex}{40}|(?:\p{AHex}{4} ){5}(?: \p{AHex}{4}){5})$/) { 1229 mywarn "Local-user $user_key is not a valid keyid"; 1230 next; 1231 } 1232 1233 $user_key =~ s/^0x//; 1234 $user_key =~ y/ //d; 1235 if (my @matching_keyids = grep {$user_key =~ /\Q$_\E$/i or /\Q$user_key\E$/i} @{$CONFIG{'keyid'}}) { 1236 push @local_user, @matching_keyids; # @{$CONFIG{'keyid'}} is always a list of long keyids 1237 } else { 1238 mywarn "Local-user $user_key is not defined as one of your keyid in ~/.caffrc (it will not be used)"; 1239 } 1240 } 1241 1242 # If no local-user key are valid, there is no need to go further 1243 myerror(1, "None of the local-user keys seem to be known as a keyid listed in ~/.caffrc") unless @local_user; 1244 return @local_user; 1245} 1246 1247## 1248# Import keys from a gnupghome to another. 1249# 1250# @param keyids keyids of the OpenPGP keys to import 1251# @param src_gnupghome gnupghome directory where to export the key from 1252# @param dst_gnupghome gnupghome directory where to import the key into 1253# @param die_on_error whether to die if some of the keyids couldn't be imported 1254# @param import_options a list of import-options, see gpg(1) 1255# 1256# @ return a hash reference mapping each key ID to the list of matching 1257# imported key fingerprint. 1258# 1259sub import_keys_from_gnupghome($$$$@) { 1260 my ($keyids, $src_gpghome, $dst_gpghome, $die_on_error, @import_options) = @_; 1261 my $src = $src_gpghome // "your normal GnuPGHOME"; 1262 my $dst = $dst_gpghome // "your normal GnuPGHOME"; 1263 1264 my @extra_args; 1265 push @import_options, 'import-local-sigs' if $CONFIG{'gpg-sign-type'} =~ /l/ and !grep /import-local-sigs$/, @import_options; 1266 push @import_options, 'keep-ownertrust' unless defined $dst_gpghome or GnuPG_version('2.1.0') >= 0; # don't modify our own trustdb 1267 push @extra_args, '--min-cert-level=1' if grep { $_ eq 'import-clean' } @import_options; 1268 push @extra_args, '--import-options', join (',', @import_options) if @import_options; 1269 1270 # export the (non-armored) keys to $pipe 1271 debug("Exporting key(s) ".(join ',', @$keyids)." from $src to $dst"); 1272 my @export_options = ('export-local-sigs') if grep {$_ eq 'import-local-sigs'} @import_options; 1273 my ($ePid, $pipe) = export_keys($src_gpghome, $keyids, @export_options); 1274 1275 my $gpg = mkGnuPG( homedir => $dst_gpghome, quiet => 1, extra_args => \@extra_args ); 1276 my $handles = mkGnuPG_fds( stdin => $pipe, status => undef ); # import keys from $pipe 1277 my $iPid = $gpg->import_keys( handles => $handles ); 1278 1279 my $status = import_loop($handles->{status}, defined $src_gpghome ? 0 : 1, $keyids); 1280 done_gpg($iPid, $handles); # import done 1281 waitpid $ePid => 0; # export done 1282 1283 my @failed = grep { !@{$status->{$_}} } keys %$status; 1284 if (@failed) { 1285 my $msg = "Couldn't import key(s) ".(join ',', @failed)." from $src"; 1286 $die_on_error ? myerror(1, $msg) : info($msg, 0); 1287 } 1288 return $status; 1289} 1290 1291## 1292# Import loop. 1293# 1294# @param fh the status file handle from GnuPG::Interface 1295# @param verbose whether to list the status of each key as it 1296# is being imported. 1297# @param keyids an array of keyids to be imported 1298# @param ignore_unexpected whether not to print a warning upon receiving 1299# an unexpected key 1300# 1301# @ return a hash reference mapping each key ID to the list of matching 1302# imported key fingerprint. 1303# 1304sub import_loop($$$;$) { 1305 my ($fh, $verbose, $keyids, $ignore_unexpected) = @_; 1306 1307 # [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F 1308 # [GNUPG:] NODATA 1 1309 # [GNUPG:] NODATA 1 1310 # [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039 1311 my %status = map { $_ => [] } @$keyids; 1312 while (<$fh>) { 1313 # inspect the $status FD as data gets out 1314 if (/^\[GNUPG:\] IMPORT_OK (\d+) ([0-9A-F]{40})$/) { 1315 my ($r, $fpr) = ($1, $2); 1316 my @matching_keyids = grep { $fpr =~ /\Q$_\E$/ } @$keyids; 1317 unless (@matching_keyids) { 1318 unless ($ignore_unexpected) { 1319 mywarn("Imported unexpected key $fpr. Are you trying to work on a subkey?"); 1320 } elsif ($verbose) { 1321 info( "Key $fpr ". ($r == 0 ? 'not changed' : 'imported'), ($r == 0 ? undef : 1) ); 1322 } 1323 next; 1324 } 1325 debug( "Imported $fpr for ".join(',', @matching_keyids)); 1326 info( "Key " .join(',', @matching_keyids).' '. ($r == 0 ? 'not changed' : 'imported'), ($r == 0 ? undef : 1) ) if $verbose; 1327 push @{$status{$_} //= []}, $fpr foreach @matching_keyids; 1328 } 1329 elsif (/^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{32})$/) { 1330 mywarn("Imported v3 key $1. Version 3 keys are obsolete, should not be used, and are not and will not be properly supported."); 1331 } 1332 elsif (!/^\[GNUPG:\]\ (?:NODATA\ \d 1333 | IMPORT_RES\ .+ 1334 | IMPORTED\ .+ 1335 | KEYEXPIRED\ \d+ 1336 | SIGEXPIRED\ (?:\ deprecated-use-keyexpired-instead)? 1337 | KEY_CONSIDERED\ [0-9A-F]{40}\ \d+ 1338 | FAILURE\ recv-keys\ \d+ 1339 )$/x) { 1340 mywarn("Got unknown reply from gpg: ".$_); 1341 } 1342 } 1343 return \%status; 1344} 1345 1346## 1347# Import keys to be signed into caff gnupghome directory. 1348# 1349# This function imports the keys the user wants to sign into the caff gnupghome 1350# directory. We looks for the keys in the the user gnupghome directory first, 1351# and in the key files specified by the user if not all of the keys have been 1352# found. 1353# 1354sub import_keys_to_sign($) { 1355 my $keyids = shift; 1356 return unless $CONFIG{'keys-from-gnupg'} or @{$CONFIG{'key-files'}} or !$CONFIG{'no-download'}; 1357 1358 # map each keyid to a list of matching fingerprints; there is a 1359 # collision if a keyid is mapped to multiple fingerprints, but we'll 1360 # detect that later in the code 1361 my $status = { map { $_ => [] } @$keyids }; 1362 1363 if ($CONFIG{'keys-from-gnupg'}) { 1364 notice("Importing keys from your normal GnuPGHOME (".($ENV{'GNUPGHOME'} // "~/.gnupg").")"); 1365 merge_import_status( $status, import_keys_from_gnupghome($keyids, undef, $GNUPGHOME, 0) ); 1366 } 1367 1368 foreach my $keyfile (@{$CONFIG{'key-files'}}) { 1369 notice("Importing key file $keyfile"); 1370 1371 my $gpg = mkGnuPG( homedir => $GNUPGHOME, quiet => 1 ); 1372 $gpg->options->push_extra_args(qw/--import-options import-local-sigs/) if $CONFIG{'gpg-sign-type'} =~ /l/; 1373 my $handles = mkGnuPG_fds( status => undef ); 1374 my $pid = $gpg->import_keys( handles => $handles, command_args => $keyfile ); 1375 1376 merge_import_status( $status, import_loop($handles->{status}, 1, $keyids, 1) ); 1377 done_gpg($pid, $handles); 1378 } 1379 1380 # Receive keys from keyserver 1381 unless ($CONFIG{'no-download'}) { 1382 notice("Fetching keys from a keyserver (this may take a while)..."); 1383 my @args = (extra_args => ['--keyserver='.$CONFIG{'keyserver'}]) if defined $CONFIG{'keyserver'}; 1384 my $gpg = mkGnuPG( homedir => $GNUPGHOME, @args ); 1385 # logger: requesting key ... from hkp 1386 # stdout: gpgkeys: key ... not found on keyserver 1387 my $handles = mkGnuPG_fds( status => undef ); 1388 my $pid = $gpg->recv_keys(handles => $handles, command_args => $keyids); 1389 1390 my $s = import_loop($handles->{status}, 1, $keyids); 1391 merge_import_status($status, $s); 1392 done_gpg($pid, $handles); 1393 1394 my @failed = grep { !@{$s->{$_}} } keys %$s; 1395 info("Couldn't import key(s) ".(join ',', @failed)." from the keyserver", 0) if @failed; 1396 } 1397 1398 my @failed = grep { !@{$status->{$_}} } keys %$status; 1399 if (@failed) { 1400 exit 1 unless ask ("Some keys could not be imported - continue anyway?", 0); 1401 mywarn("Assuming ". join(' ', @failed).' '.($#failed > 0 ? 'are' : 'is a')." fine keyid".($#failed > 0 ? 's' : '')); 1402 } 1403} 1404sub merge_import_status($$) { 1405 foreach my $keyid (keys %{$_[1]}) { 1406 push @{$_[0]->{$keyid} //= []}, @{$_[1]->{$keyid}}; 1407 } 1408} 1409 1410## 1411# A non-localized version of POSIX::strftime. 1412# 1413sub strfCtime($@) { 1414 my $lc_time = setlocale(POSIX::LC_TIME); 1415 setlocale(POSIX::LC_TIME, 'C'); 1416 my $str = strftime(@_); 1417 setlocale(POSIX::LC_TIME, $lc_time); 1418 return $str; 1419} 1420 1421 1422################### 1423# argument handling 1424################### 1425Getopt::Long::config('bundling'); 1426if (!GetOptions ( 1427 '-h' => \$PARAMS->{'help'}, 1428 '--help' => \$PARAMS->{'help'}, 1429 '--version' => \$PARAMS->{'version'}, 1430 '-V' => \$PARAMS->{'version'}, 1431 '-u=s' => \$PARAMS->{'local-user'}, 1432 '--local-user=s' => \$PARAMS->{'local-user'}, 1433 '-e' => \$PARAMS->{'export-old'}, 1434 '--export-old' => \$PARAMS->{'export-old'}, 1435 '-E' => \$PARAMS->{'no-export-old'}, 1436 '--no-export-old' => \$PARAMS->{'no-export-old'}, 1437 '-m:s' => \$PARAMS->{'mail'}, 1438 '--mail:s' => \$PARAMS->{'mail'}, 1439 '-M' => \$PARAMS->{'no-mail'}, 1440 '--no-mail' => \$PARAMS->{'no-mail'}, 1441 '-R' => \$PARAMS->{'no-download'}, 1442 '--no-download' => \$PARAMS->{'no-download'}, 1443 '-S' => \$PARAMS->{'no-sign'}, 1444 '--no-sign' => \$PARAMS->{'no-sign'}, 1445 '--key-file=s@' => \$PARAMS->{'key-files'}, 1446 '--keys-from-gnupg' => \$PARAMS->{'keys-from-gnupg'}, 1447 '--debug' => \$PARAMS->{'debug'}, 1448 )) { 1449 usage(\*STDERR, 1); 1450} 1451if ($PARAMS->{'help'}) { 1452 usage(\*STDOUT, 0); 1453} 1454if ($PARAMS->{'version'}) { 1455 version(\*STDOUT); 1456 exit(0); 1457} 1458 1459load_config(); 1460 1461my $NOW = time; 1462my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($NOW); 1463my $DATE_STRING = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday); 1464 1465 1466if (-t \*STDIN) { 1467 # we're already talking to a TTY 1468 usage(\*STDERR, 1) unless @ARGV; 1469 *TTY = *STDIN; 1470} else { 1471 my @checksums; 1472 my $goodblock; 1473 my $got_input; # detect xargs, /dev/null, ... 1474 while (<STDIN>) { 1475 unless ($got_input) { 1476 notice("Reading gpgparticipants formatted input on STDIN"); 1477 $got_input = 1; 1478 } 1479 1480 if (/^(\S+)\s+Checksum:\s+[_ 0-9A-F]+(?:\s+\[(.)\])?$/i) { 1481 # ensure the checksum is (claimed to be) verified 1482 my ($md, $r) = ($1, $2); 1483 while (!defined $r) { 1484 $_ = <STDIN>; 1485 if (/^\s+[_ 0-9A-F]+\s+\[(.)\]$/i) { 1486 $r = $1; 1487 } 1488 elsif (!/^(:?\s+[_ 0-9A-F]+)?$/i) { 1489 myerror(1, "Unexpected input line: $_"); 1490 } 1491 } 1492 myerror(1, "$md checksum wasn't marked as verified!") unless lc $r eq 'x'; 1493 notice "Found $md checksum (marked as verified, assumed good)"; 1494 push @checksums, uc $md; 1495 } 1496 elsif (/^(?:-+|_+)$/) { 1497 $goodblock = 0; 1498 } 1499 elsif (/^(#*)\d*\s+\[(.)\] Fingerprint(?:\(s\)|s)? OK\s+\[(.)\] ID OK\s*$/) { 1500 $goodblock = (!$1 and lc $2 eq 'x' and lc $3 eq 'x') ? 1 : 0; 1501 } 1502 elsif (/^ {5,}Key fingerprint = ([A-F0-9]{32}|(?:[A-F0-9]{2} ){8}(?: [A-F0-9]{2}){8})$/) { 1503 mywarn("Ignoring v3 fingerprint ".($1 =~ y/ //dr).". v3 keys are obsolete."); 1504 } 1505 elsif (/^ {5,}(?:Key fingerprint = )?([A-F0-9]{40}|(?:[A-F0-9]{4} ){5}(?: [A-F0-9]{4}){5})$/) { 1506 my $fpr = ($1 =~ y/ //dr); 1507 if ($goodblock) { 1508 info("Adding fingerprint $fpr", 1); 1509 push @KEYIDS, $fpr; 1510 } else { 1511 info("Ignoring fingerprint $fpr", 0); 1512 } 1513 } 1514 } 1515 1516 if ($got_input) { 1517 if (!@checksums) { 1518 mywarn "No checksum found!"; 1519 } elsif (!grep { my $x = $_; grep { $x eq $_ } qw/SHA256 SHA384 SHA512 SHA224/ } @checksums) { 1520 mywarn "No checksum of the SHA-2 family found!"; 1521 } 1522 } 1523 1524 close STDIN; 1525 open TTY, '<', '/dev/tty' or myerror(1,"No TTY.") 1526} 1527my $TERMIOS = do { 1528 my $t = POSIX::Termios::->new(); 1529 $t->getattr(fileno(TTY)) // die "getattr: $!"; 1530 $t; 1531}; 1532 1533for my $hashkey (qw{local-user no-download no-sign no-mail mail keys-from-gnupg}) { 1534 $CONFIG{$hashkey} = $PARAMS->{$hashkey} if defined $PARAMS->{$hashkey}; 1535} 1536 1537# If old 'no-mail' parameter, or if the 'mail' parameter is set to 'no' 1538if ( defined $CONFIG{'no-mail'} || (defined $CONFIG{'mail'} && $CONFIG{'mail'} eq 'no') ) { 1539 $CONFIG{'mail'} = 'no'; 1540} elsif ( !defined $CONFIG{'mail'} ) { 1541 $CONFIG{'mail'} = 'ask-yes'; 1542} 1543$CONFIG{'mail-cant-encrypt'} //= $CONFIG{'mail'}; 1544 1545push @{$CONFIG{'key-files'}}, @{$PARAMS->{'key-files'}} if defined $PARAMS->{'key-files'}; 1546 1547for my $keyid (map { split /\n/ } @ARGV) { # caff "`cat txt`" is a single argument 1548 if ($keyid =~ /^(\p{AHex}{32}|(?:\p{AHex}{2} ){8}(?: \p{AHex}{2}){8})$/) { 1549 mywarn("Ignoring v3 fingerprint ".($keyid =~ y/ //dr).". v3 keys are obsolete."); 1550 next; 1551 } 1552 elsif ($keyid !~ /^((?:0x)?\p{AHex}{8}|(?:0x)?\p{AHex}{16}|\p{AHex}{40}|(?:\p{AHex}{4} ){5}(?: \p{AHex}{4}){5})$/) { 1553 print STDERR "$keyid is not a keyid.\n"; 1554 usage(\*STDERR, 1); 1555 } 1556 1557 $keyid =~ s/^0x//; 1558 $keyid =~ y/ //d; # gpg --fingerprint includes spaces 1559 push @KEYIDS, uc($keyid); 1560} 1561 1562 1563if (GnuPG_version('2.1.0') >= 0) { 1564 my %sockets; 1565 unless ($CONFIG{'no-sign'}) { 1566 # Ensure we have a working agent for our secret key material 1567 my $secdir = $CONFIG{'secret-keyring'}; 1568 $secdir =~ s#/[^/]+$## unless -d $secdir; 1569 mysystem('gpg-connect-agent', '--homedir', $secdir, '/bye'); 1570 $sockets{'agent-socket'} = GnuPG_version('2.1.13') < 0 ? 1571 # gpgconf < 2.1.13 doesn't understand --homedir; but on 1572 # these versions the gpg-agent socket path is always 1573 # $GNUPGHOME/S.gpg-agent. 1574 "$secdir/S.gpg-agent" : 1575 gpgconf('--homedir', $secdir, '--list-dirs')->{'agent-socket'}; 1576 } 1577 unless ($CONFIG{'no-download'}) { 1578 # Ensure we have a working agent for the downloads 1579 my $homedir = $ENV{'GNUPGHOME'} // "$ENV{'HOME'}/.gnupg"; 1580 mysystem('gpg-connect-agent', '--homedir', $homedir, '--dirmngr', '/bye'); 1581 $sockets{'dirmngr-socket'} = GnuPG_version('2.1.13') < 0 ? 1582 # gpgconf < 2.1.13 doesn't understand --homedir; but on 1583 # these versions the gpg-agent socket path is always 1584 # $GNUPGHOME/S.dirmngr. 1585 "$homedir/S.dirmngr" : 1586 gpgconf('--homedir', $homedir, '--list-dirs')->{'dirmngr-socket'}; 1587 } 1588 1589 foreach my $k (keys %sockets) { 1590 my $socket = $sockets{$k}; 1591 my $l = GnuPG_version('2.1.13') < 0 ? 1592 $socket =~ s#.*/#$GNUPGHOME/#r : 1593 gpgconf('--homedir', $GNUPGHOME, '--list-dirs')->{$k}; 1594 if (-l $l) { 1595 unlink $l 1596 } elsif (-S $l) { 1597 # don't run agents in caff's homedir 1598 myerror(1, "$l: socket exists; runaway gpg-agent?"); 1599 } elsif (! -S $socket) { 1600 myerror(1, "Missing socket $socket"); 1601 } 1602 debug "Creating symlink $l to $socket"; 1603 symlink $socket, $l or myerror(1, "Cannot symlink: $!"); 1604 } 1605} 1606elsif ($CONFIG{'also-lsign-in-gnupghome'} eq 'auto' and $CONFIG{'gpg-sign-type'} !~ /l/) { 1607 # Ensure there is a working gpg-agent if $CONFIG{'also-lsign-in-gnupghome'} is 'auto' 1608 system qw/gpg-agent -q/; 1609 unless ($? == 0) { 1610 mywarn("No gpg-agent running: set \$CONFIG{'also-lsign-in-gnupghome'} = 'ask'"); 1611 $CONFIG{'also-lsign-in-gnupghome'} = 'ask'; 1612 } 1613} 1614 1615################################## 1616# import own keys and keys to sign 1617################################## 1618import_keys_from_gnupghome($CONFIG{'keyid'}, undef, $GNUPGHOME, 1); 1619import_keys_from_gnupghome($CONFIG{'also-encrypt-to'}, undef, $GNUPGHOME, 0) if defined $CONFIG{'also-encrypt-to'}; 1620@LOCAL_USER = get_local_user_keys() unless $CONFIG{'no-sign'}; 1621 1622import_keys_to_sign(\@KEYIDS); 1623if ($CONFIG{'ask-sign'} && ! $CONFIG{'no-sign'}) { 1624 $CONFIG{'no-sign'} = ! ask("Continue with signing?", 1); 1625} 1626 1627my %KEYS; 1628for my $keyid (@KEYIDS) { 1629 # get key listing (and ensure there is no collision) 1630 #################################################### 1631 my $gpg = mkGnuPG( homedir => $GNUPGHOME, extra_args => ['--with-fingerprint', '--with-colons'] ); 1632 my $handles = mkGnuPG_fds( stdout => undef ); 1633 1634 # process the keys one by one so we can detect collisions 1635 my $pid = $gpg->list_public_keys( handles => $handles, command_args => [$keyid] ); 1636 1637 my @matching_keys; 1638 while (readline $handles->{stdout}) { 1639 if (/^pub:([^:]+):(?:[^:]*:){2}([0-9A-F]{16}):(?:[^:]*:){6}([^:]+)/) { 1640 push @matching_keys, { validity => $1, longkeyid => $2, capability => $3, uids => [], subkeys => [] }; 1641 } 1642 elsif (/^fpr:(?:[^:]*:){8}([0-9A-F]{40}|[0-9A-F]{32})(?::.*)?$/) { 1643 $matching_keys[$#matching_keys]->{fpr} //= $1; 1644 } 1645 elsif (/^sub:[^:]+:(?:[^:]*:){2}([0-9A-F]{16}):/) { 1646 push @{$matching_keys[$#matching_keys]->{subkeys}}, $1; 1647 } 1648 elsif (/^(uid|uat):([^:]+):(?:[^:]*:){5}([0-9A-F]{40}):[^:]*:([^:]+)/) { 1649 my $uid = { type => $1 1650 , validity => $2 1651 , hash => $3 1652 , text => $1 eq 'uid' ? $4 : '[attribute]' 1653 }; 1654 $uid->{text} =~ s/\\x(\p{AHex}{2})/ chr(hex($1)) /ge; 1655 # --with-colons always outputs UTF-8 1656 $uid->{text} = Encode::decode_utf8($uid->{text}); 1657 $uid->{address} = email_valid $uid->{text} if $uid->{type} eq 'uid'; 1658 push @{$matching_keys[$#matching_keys]->{uids}}, $uid; 1659 } 1660 elsif (!/^(?:rvk|tru):/) { 1661 chomp; 1662 mywarn("Got unknown reply from gpg: ".$_); 1663 } 1664 } 1665 done_gpg($pid, $handles); 1666 1667 unless (@matching_keys) { 1668 mywarn("No public keys found with list-key $keyid (note that caff uses its own keyring in $GNUPGHOME)"); 1669 next; 1670 } 1671 1672 my $key; 1673 foreach (@matching_keys) { 1674 my $reason = $_->{fpr} =~ /^\p{AHex}{32}$/ ? 'obsolete v3' 1675 : $_->{validity} =~ /e/ ? 'expired' 1676 : $_->{validity} =~ /i/ ? 'invalid' 1677 : $_->{validity} =~ /r/ ? 'revoked' 1678 : $_->{capability} =~ /D/ ? 'disabled' 1679 : do { $key = $_; last }; 1680 mywarn("Ignoring $reason key $_->{fpr}"); 1681 } 1682 mywarn( "More than one key matched $keyid (assuming $key->{fpr}). " 1683 . "Try to specify the long keyid or full fingerprint to avoid collisions.") 1684 if $#matching_keys > 0 and defined $key; 1685 1686 if (defined $key) { 1687 $KEYS{$keyid} = $key; 1688 } else { 1689 my $msg = "public key found with list-key $keyid"; 1690 mywarn( @matching_keys ? "No valid $msg" : "No $msg (note that caff uses its own keyring in $GNUPGHOME)" ); 1691 } 1692} 1693 1694unless (keys %KEYS) { 1695 notice("No keys to sign found", 0); 1696 exit 0; 1697} 1698 1699 1700for my $keyid (@KEYIDS) { 1701 next unless exists $KEYS{$keyid}; 1702 my ($longkeyid, $fpr) = @{$KEYS{$keyid}}{qw/longkeyid fpr/}; 1703 1704 ########### 1705 # sign keys 1706 ########### 1707 unless ($CONFIG{'no-sign'}) { 1708 notice("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key"); 1709 foreach my $local_user (@LOCAL_USER) { 1710 my @command = ($CONFIG{'gpg'}); 1711 push @command, "--homedir=$GNUPGHOME"; 1712 push @command, @GNUPGOPTS if @GNUPGOPTS; 1713 push @command, '--secret-keyring', $CONFIG{'secret-keyring'} if GnuPG_version('2.1.0') < 0; 1714 push @command, qw/--no-auto-check-trustdb --trust-model=always/; 1715 push @command, '--local-user', $local_user; 1716 push @command, '--edit-key', $fpr; 1717 push @command, 'showphoto' if $CONFIG{'show-photos'}; 1718 push @command, $CONFIG{'gpg-sign-type'}.'sign'; 1719 push @command, split ' ', $CONFIG{'gpg-sign-args'} || ""; 1720 print join(' ', @command),"\n"; 1721 mysystem(@command); 1722 }; 1723 }; 1724 1725 1726 ################## 1727 # export and prune 1728 ################## 1729 1730 # export the key 1731 ################ 1732 my $keydir = File::Temp->newdir( "caff-$keyid-XXXXX", TMPDIR => 1 ); 1733 # we can't use only one import here because the cleaning is done as the 1734 # keys come and our keys might not be imported yet 1735 import_keys_from_gnupghome($CONFIG{'keyid'}, $GNUPGHOME, $keydir, 1, 'import-minimal', 'import-local-sigs'); 1736 import_keys_from_gnupghome([$fpr], $GNUPGHOME, $keydir, 1, 'import-clean', 'import-local-sigs'); 1737 1738 # the first UID. we won't delete that one when pruning for UATs because a key has to have at least one UID 1739 my @uids = @{$KEYS{$keyid}->{uids}}; 1740 my $first_uid = (grep {$_->{type} eq 'uid'} @uids)[0]; 1741 1742 for (my $uid_number = 1; $uid_number <= $#uids+1; $uid_number++) { 1743 debug("Doing key $keyid, uid $uid_number"); 1744 my $uid = $uids[$uid_number-1]; 1745 1746 # /!\ this serial is valid in caff's GnuPGHOME only, and can't 1747 # be relied upon if the keyring is modified in the meantime. 1748 $uid->{serial} = $uid_number; 1749 1750 next if $uid->{validity} =~ /[eir]/; # skip expired / invalid / revokey UIDs 1751 1752 # copy pubring to temporary gpghome 1753 ################################### 1754 my $uiddir = File::Temp->newdir( "caff-$keyid-$uid_number-XXXXX", TMPDIR => 1 ); 1755 foreach (qw/pubring.gpg pubring.kbx/) { 1756 copy($keydir.'/'.$_, $uiddir.'/'.$_) if -e $keydir.'/'.$_; 1757 } 1758 1759 # prune it 1760 ########## 1761 my $gpg = mkGnuPG( homedir => $uiddir, extra_args => ['--with-colons'] ); 1762 my $handles = mkGnuPG_fds( command => undef, stdout => undef, status => undef ); 1763 my $pid = $gpg->wrap_call( 1764 commands => [ '--edit-key' ], 1765 command_args => [ $fpr ], 1766 handles => $handles ); 1767 1768 debug("Starting edit session"); 1769 my %output = readwrite_gpg($handles, status => $KEYEDIT_PROMPT); 1770 1771 # delete other uids 1772 ################### 1773 my $delete_some = 0; 1774 for (my $i = 1; $i <= $#uids+1; $i++) { 1775 # it's quicker with gpg2: 'uid *' then 'uid $i' 1776 next if $i == $uid_number; 1777 next if $uid->{type} ne 'uid' and $uids[$i-1]->{hash} eq $first_uid->{hash}; # keep the first UID 1778 1779 debug("Marking UID $i ($uids[$i-1]->{hash}) for deletion"); 1780 readwrite_gpg($handles, command => "uid $i", status => $KEYEDIT_PROMPT); 1781 $delete_some++; 1782 } 1783 1784 if ($delete_some) { 1785 debug("Need to delete $delete_some uids"); 1786 readwrite_gpg($handles, command => "deluid", status => $KEYEDIT_DELUID_PROMPT); 1787 readwrite_gpg($handles, command => "yes", status => $KEYEDIT_PROMPT); 1788 }; 1789 1790 # delete all subkeys 1791 #################### 1792 if (@{$KEYS{$keyid}->{subkeys}}) { 1793 for (my $i = 1; $i <= $#{$KEYS{$keyid}->{subkeys}} + 1; $i++) { 1794 debug("Marking subkey $i ($KEYS{$keyid}->{subkeys}->[$i-1]) for deletion"); 1795 readwrite_gpg($handles, command => "key $i", status => $KEYEDIT_PROMPT); 1796 }; 1797 readwrite_gpg($handles, command => "delkey", status => $KEYEDIT_DELSUBKEY_PROMPT); 1798 readwrite_gpg($handles, command => "yes", status => $KEYEDIT_PROMPT); 1799 }; 1800 1801 # delete signatures 1802 ################### 1803 # this shouldn't delete anything as $longkeyid is already clean, but maybe we didn't sign that uid with all keys in @{$CONFIG{'keyid'}} 1804 my ($last_signed_on, $xsigners) = delete_signatures($handles, $longkeyid, $uid->{hash}, $CONFIG{'keyid'}); 1805 1806 delete_signatures($handles, $longkeyid, $first_uid->{hash}, []) 1807 if $uid->{type} ne 'uid'; # delete all sigs on the first UID if $uid is an attribute 1808 1809 1810 readwrite_gpg($handles, command => "save"); 1811 done_gpg($pid, $handles); 1812 debug("Done editing"); 1813 1814 my $asciikey = export_keys($uiddir, [$fpr], 'export-local-sigs'); 1815 undef $uiddir; # delete dir 1816 1817 unless ($asciikey) { 1818 mywarn "No data from gpg for export $fpr"; 1819 next; 1820 }; 1821 1822 if ($last_signed_on) { 1823 # it's a bit inefficient to store the $asciikey in memory, 1824 # but it has been pruned so it's shouldn't be too big 1825 $uid->{key} = $asciikey; 1826 $uid->{xsigners} = $xsigners; 1827 $uid->{last_signed_on} = $last_signed_on; 1828 }; 1829 }; 1830 1831 1832 unless ($CONFIG{'also-lsign-in-gnupghome'} eq 'no') { 1833 # remove all exportable sigs, and import into our GnuPGHOME 1834 ########################################################### 1835 my $gpg = mkGnuPG( homedir => $keydir, extra_args => ['--with-colons'] ); 1836 my $handles = mkGnuPG_fds( command => undef, stdout => undef, status => undef ); 1837 my $pid = $gpg->wrap_call( 1838 commands => [ '--edit-key' ], 1839 command_args => [ $fpr ], 1840 handles => $handles ); 1841 1842 debug("Starting edit session on $keyid"); 1843 my %output = readwrite_gpg($handles, status => $KEYEDIT_PROMPT); 1844 delete_signatures($handles, $longkeyid, $uids[$_]->{hash}, $CONFIG{'keyid'}, 1) foreach (0 .. $#uids); 1845 1846 readwrite_gpg($handles, command => "save"); 1847 done_gpg($pid, $handles); 1848 debug("Done editing"); 1849 1850 # import the pruned keys with our own local sigs only; this is 1851 # required even if there are no lsigs, to ensure we've got all 1852 # UIDs in our own GnuPGHOME 1853 import_keys_from_gnupghome( [$fpr], $keydir, undef, 1, 'import-local-sigs' ); 1854 } 1855 undef $keydir; # delete dir 1856 1857 if ($CONFIG{'also-lsign-in-gnupghome'} eq 'ask') { 1858 # manually lsign the key 1859 ######################## 1860 foreach my $local_user (@LOCAL_USER) { 1861 my @command = ($CONFIG{'gpg'}); 1862 push @command, '--secret-keyring', $CONFIG{'secret-keyring'} if GnuPG_version('2.1.0') < 0; 1863 push @command, qw/--no-auto-check-trustdb --trust-model=always/; 1864 push @command, '--local-user', $local_user; 1865 push @command, '--edit-key', $fpr; 1866 push @command, 'showphoto' if $CONFIG{'show-photos'}; 1867 push @command, 'lsign'; 1868 push @command, split ' ', $CONFIG{'gpg-sign-args'} || ""; 1869 print join(' ', @command),"\n"; 1870 mysystem(@command); 1871 } 1872 } 1873 elsif ($CONFIG{'also-lsign-in-gnupghome'} eq 'auto') { 1874 # auto lsign the uids we for which we have an exportable sig 1875 ############################################################ 1876 my @uids = grep {exists $_->{xsigners}} @{$KEYS{$keyid}->{uids}}; 1877 my @signers = map {keys %{$_->{xsigners}}} @uids; 1878 # which of @LOCAL_USER has signed at least one UID in this key? 1879 @signers = grep { my $u = $_; grep { $u eq $_ } @signers } @LOCAL_USER; 1880 @signers = keys %{{ map { $_ => 1 } @signers }}; # remove duplicates to avoid double signing 1881 1882 foreach my $u (@signers) { 1883 my @signeduids; # uids signed by $u 1884 foreach my $uid (@uids) { 1885 # we use UIDs hashes to distinguish and select UIDs; it's the only reliable way to identify them across keyrings 1886 push @signeduids, $uid if grep { $u eq $_ } (keys %{$uid->{xsigners}}) and 1887 !grep { $uid->{hash} eq $_->{hash} } @signeduids; 1888 } 1889 1890 my $gpg = mkGnuPG( extra_args => ['--local-user' => $u, '--ask-cert-level', '--with-colons', '--no-batch'] ); 1891 $gpg->options->push_extra_args('--secret-keyring', $CONFIG{'secret-keyring'}) if GnuPG_version('2.1.0') < 0; 1892 $gpg->options->push_extra_args('--use-agent') if GnuPG_version('2.0.0') < 0; # we know there is a working agent 1893 my $handles = mkGnuPG_fds( command => undef, stdout => undef, status => undef ); 1894 my $pid = $gpg->wrap_call( 1895 commands => [ '--edit-key' ], 1896 command_args => [ $fpr ], 1897 handles => $handles ); 1898 1899 debug("Starting edit session on $keyid, signer $u"); 1900 readwrite_gpg($handles, status => $KEYEDIT_PROMPT); 1901 1902 foreach my $level (0..3) { 1903 my @signeduids_with_level = grep {$_->{xsigners}->{$u} eq $level} @signeduids; 1904 next unless @signeduids_with_level; 1905 1906 notice("Key $longkeyid UID(s) #".(join ',', sort (map {$_->{serial}} @signeduids_with_level)).": lsign'ing with $u, cert level $level", 1); 1907 readwrite_gpg($handles, command => "uid 0", status => $KEYEDIT_PROMPT); # unselect UIDs 1908 readwrite_gpg($handles, command => "uid $_->{hash}", status => $KEYEDIT_PROMPT) for @signeduids_with_level; 1909 my %output = readwrite_gpg($handles, command => "lsign", status => qr/$KEYEDIT_SIGNUID_CLASS_PROMPT|$KEYEDIT_PROMPT/); 1910 next if $output{status} =~ /^\[GNUPG:\] $KEYEDIT_PROMPT/m; # already signed 1911 readwrite_gpg($handles, command => $level, status => $KEYEDIT_SIGNUID_PROMPT); 1912 readwrite_gpg($handles, command => "yes", status => $KEYEDIT_PROMPT); 1913 } 1914 1915 readwrite_gpg($handles, command => "save"); 1916 done_gpg($pid, $handles); 1917 debug("Done editing"); 1918 } 1919 } 1920} 1921 1922############# 1923# send emails 1924############# 1925for my $keyid (@KEYIDS) { 1926 next unless exists $KEYS{$keyid}; 1927 my $longkeyid = $KEYS{$keyid}->{longkeyid}; 1928 my $can_encrypt = $KEYS{$keyid}->{capability} =~ /E/; 1929 my @uids = @{$KEYS{$keyid}->{uids}}; 1930 1931 unless (grep {$_->{last_signed_on}} @uids) { 1932 info("Key 0x$longkeyid has no signed uids, skipping", 0); 1933 next; 1934 } 1935 1936 my @attached; 1937 for my $uid (@uids) { 1938 my $text = defined $LOCALE ? $LOCALE->encode($uid->{text}) : $uid->{text}; 1939 1940 trace("UID: $text\n"); 1941 if ($uid->{validity} =~ /[eir]/) { 1942 my $reason = $uid->{validity} =~ /e/ ? 'expired' : 1943 $uid->{validity} =~ /i/ ? 'invalid' : 1944 $uid->{validity} =~ /r/ ? 'revoked' : die; 1945 info("Key 0x$longkeyid ".(uc $uid->{type})." $uid->{serial} $text is $reason, skipping", 0); 1946 next; 1947 } 1948 unless ($uid->{last_signed_on}) { 1949 info("Key 0x$longkeyid ".(uc $uid->{type})." $uid->{serial} $text is not signed by me, skipping", 0); 1950 next; 1951 } 1952 1953 if ($NOW - $uid->{last_signed_on} > $CONFIG{'export-sig-age'} and 1954 !ask("Signature on $text is old. Export?", 0, $PARAMS->{'export-old'}, $PARAMS->{'no-export-old'})) { 1955 next; 1956 } 1957 1958 # save the armored key 1959 my $keydir = "$KEYSBASE/$DATE_STRING"; 1960 -d $keydir || mkdir $keydir, 0700 or myerror(1, "Cannot mkdir $keydir: $!"); 1961 1962 my $keyfile = "$keydir/$longkeyid.key.$uid->{serial}.".sanitize_uid($text).".asc"; 1963 open my $KEY, '>', $keyfile or myerror(1, "Cannot open $keyfile: $!"); 1964 debug "Writing armored key 0x$longkeyid to $keyfile"; 1965 print $KEY $uid->{key}; 1966 close $KEY; 1967 1968 if ($uid->{type} eq 'uat') { 1969 if (ask("UID $text is an attribute UID, attach it to every email?", 1)) { 1970 push @attached, $uid; 1971 $uid->{export} = 1; 1972 } 1973 } elsif (!defined $uid->{address}) { 1974 if (ask("UID $text is no email address, attach it to every email?", 1)) { 1975 push @attached, $uid; 1976 $uid->{export} = 1; 1977 } 1978 } 1979 else { 1980 $uid->{export} = 1; 1981 } 1982 1983 info("Key 0x$longkeyid ".(uc $uid->{type})." $uid->{serial} $text done", 1); 1984 } 1985 1986 @uids = grep {$_->{last_signed_on}} @uids; # ignore UIDs we didn't sign 1987 delete $_->{key} foreach grep {!$_->{export}} @uids; # delete non-exported keys 1988 1989 if (!grep {defined $_->{address}} @uids) { 1990 mywarn "No signed RFC 5322 UID on $longkeyid; won't send other signed UID and attributes!" 1991 if @attached; 1992 } 1993 elsif (grep {$_->{export}} @uids) { 1994 notice("Key 0x$longkeyid has no encryption capabilities, mail(s) will be sent/stored unencrypted", 0) unless $can_encrypt; 1995 my $sendmail = $can_encrypt ? $CONFIG{'mail'} : $CONFIG{'mail-cant-encrypt'}; 1996 1997 for my $uid (@uids) { 1998 next unless defined $uid->{address}; 1999 next unless $uid->{export} or @attached; 2000 my @keys = @attached; 2001 unshift @keys, $uid if exists $uid->{key}; 2002 2003 my $mail = create_mail($uid->{address}, $can_encrypt, $longkeyid, @keys); 2004 if (defined $mail) { 2005 my @sentuids = map {$_->{text}} @attached; 2006 unshift @sentuids, $uid->{text} if $uid->{export}; 2007 do { $_ = $LOCALE->encode($_) foreach @sentuids; } if defined $LOCALE; 2008 my $text = join(', ', map {"'$_'"} @sentuids); 2009 2010 my $should_send_mail = ask("Mail ".($can_encrypt ? '' : '*unencrypted* ')."signature for $text to '$uid->{address}'?", 2011 $sendmail ne 'ask-no', $sendmail eq 'yes', $sendmail eq 'no'); 2012 send_message($mail) if $should_send_mail; 2013 2014 my $keydir = "$KEYSBASE/$DATE_STRING"; 2015 my $mailfile = "$keydir/$longkeyid.mail.".($should_send_mail ? '' : 'unsent.').$uid->{'serial'}.".".sanitize_uid($text); 2016 open my $MAILFILE, '>', $mailfile or myerror(1, "Cannot open $mailfile: $!"); 2017 debug "Writing message to $mailfile"; 2018 $mail->print($MAILFILE); 2019 close $MAILFILE; 2020 } else { 2021 mywarn "Generating mail failed"; 2022 } 2023 } 2024 } 2025 2026 info("Key 0x$longkeyid done", 1); 2027} 2028 2029END { 2030 if (defined $TERMIOS and defined (my $fd = fileno(TTY))) { 2031 $TERMIOS->setattr($fd, POSIX::TCSANOW) // warn "setattr: $!"; 2032 } 2033} 2034 2035########################### 2036# the default mail template 2037########################### 2038 2039__DATA__ 2040Hi, 2041 2042please find attached the user id{(scalar @uids >= 2 ? 's' : '')} 2043{foreach $uid (@uids) { 2044 $OUT .= "\t".$uid."\n"; 2045};}of your key {$key} signed by me. 2046 2047If you have multiple user ids, I sent the signature for each user id 2048separately to that user id's associated email address. You can import 2049the signatures by running each through `gpg --import`. 2050 2051Note that I did not upload your key to any keyservers. If you want this 2052new signature to be available to others, please upload it yourself. 2053With GnuPG this can be done using 2054 gpg --keyserver hkp://pool.sks-keyservers.net --send-key {$key} 2055 2056If you have any questions, don't hesitate to ask. 2057 2058Regards, 2059-- 2060{$owner} 2061