2 # Functions for managing gnupg keys, signing, encrypting and so on
4 BEGIN { push(@INC, ".."); };
8 our ($module_name, %config, $user_module_config_directory, %text);
11 # Only do this if we are the primary library for the usermin gnupg mod
13 &switch_to_remote_user();
14 &create_user_config_dirs();
16 &foreign_require("proc", "proc-lib.pl");
18 our $gpgpath = $config{'gpg'} || "gpg";
21 # Returns an array of all GnuPG keys
26 open(GPG, "$gpgpath --list-keys 2>/dev/null |");
28 if (/^pub\s+(\S+)\/(\S+)\s+(\S+)\s+(.*)\s+<(\S+)>/ ||
29 /^pub\s+(\S+)\/(\S+)\s+(\S+)\s+(.*)/) {
30 my $k = { 'size' => $1,
33 'name' => $4 ? [ $4 ] : [ ],
34 'email' => $5 ? [ $5 ] : $4 ? [ "" ] : [ ],
35 'index' => scalar(@rv) };
36 if ($k->{'name'}->[0] &&
37 $k->{'name'}->[0] =~ /\[(expires|expired):\s+(\S+)\]/) {
38 # Expiry date, the actual name
40 $k->{'expired'} = 1 if ($1 eq 'expired');
41 shift(@{$k->{'name'}});
43 $kmap{$k->{'key'}} = $k;
47 if (/^sub\s+(\S+)\/(\S+)\s+/) {
48 push(@{$k->{'key2'}}, $2);
50 elsif (/^uid\s+(.*)\s+<(\S+)>/ ||
52 push(@{$k->{'name'}}, $1);
53 push(@{$k->{'email'}}, $2);
60 open(GPG, "$gpgpath --list-secret-keys 2>/dev/null |");
62 if (/^sec\s+(\S+)\/(\S+)\s+(\S+)\s+(.*)/ && $kmap{$2}) {
63 $kmap{$2}->{'secret'}++;
72 # Returns a list of all keys, sorted by name
75 return sort { lc($a->{'name'}->[0]) cmp lc($b->{'name'}->[0]) }
80 # List list_keys, but only returns secret ones
83 return grep { $_->{'secret'} } &list_keys();
86 # key_fingerprint(&key)
92 open(GPG, "$gpgpath --fingerprint \"$_[0]->{'name'}->[0]\" |");
94 if (/fingerprint\s+=\s+(.*)/) {
103 # get_passphrase(&key)
106 open(PASS, "$user_module_config_directory/pass.$_[0]->{'key'}") ||
107 open(PASS, "$user_module_config_directory/pass") || return undef;
114 # put_passphrase(pass, &key)
118 &open_tempfile($fh, ">$user_module_config_directory/pass.$_[1]->{'key'}");
119 &print_tempfile($fh, $_[0],"\n");
120 &close_tempfile($fh);
121 chmod(0700, "$user_module_config_directory/pass.$_[1]->{'key'}");
124 # encrypt_data(data, &result, &key|&keys, ascii)
125 # Encrypts some data with the given public key and returns the result, and
126 # returns an error message or undef on failure
129 my $srcfile = &transname();
130 my @keys = ref($_[2]) eq 'ARRAY' ? @{$_[2]} : ( $_[2] );
131 my $rcpt = join(" ", map { "--recipient \"$_->{'name'}->[0]\"" } @keys);
132 &write_entire_file($srcfile, $_[0]);
133 my $dstfile = &transname();
134 my $ascii = $_[3] ? "--armor" : "";
135 my $comp = $config{'compress'} eq '' ? "" :
136 " --compress-algo $config{'compress'}";
138 my $cmd = "$gpgpath --output $dstfile $rcpt $ascii $comp --encrypt $srcfile";
139 my ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", $cmd);
141 my $rv = &wait_for($fh, "anyway");
143 syswrite($fh, "yes\n", length("yes\n"));
150 &reset_environment();
152 my $dst = &read_entire_file($dstfile);
159 return $wait_for_input;
163 # decrypt_data(data, &result)
164 # Decrypts some data encrypted for the current GnuPG user, and puts the results
165 # into &result. Returns an error message or undef on success.
168 my $srcfile = &transname();
169 &write_entire_file($srcfile, $_[0]);
170 my $dstfile = &transname();
172 my $cmd = "$gpgpath --output $dstfile --decrypt $srcfile";
173 my ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", $cmd);
174 my ($error, $seen_pass, $pass, $key, $keyid);
176 my $rv = &wait_for($fh, "passphrase:", "key,\\s+ID\\s+(\\S+),", "failed.*\\n", "error.*\\n", "invalid.*\\n", "signal caught.*\\n");
178 last if ($seen_pass++);
180 syswrite($fh, "$pass\n", length("$pass\n"));
183 $keyid = $matches[1];
184 ($key) = grep { &indexof($matches[1], @{$_->{'key2'}}) >= 0 }
186 $pass = &get_passphrase($key) if ($key);
197 &reset_environment();
199 my $dst = &read_entire_file($dstfile);
202 return $text{'gnupg_ecryptid'};
205 return &text('gnupg_ecryptkey', "<tt>$keyid</tt>");
207 elsif (!defined($pass)) {
208 return &text('gnupg_ecryptpass', $key->{'name'}->[0]).". ".
209 &text('gnupg_canset', "/gnupg/edit_key.cgi?key=$key->{'key'}").".";
211 elsif ($error || $seen_pass > 1) {
212 return "<pre>$wait_for_input</pre>";
220 # sign_data(data, \&result, &key, mode)
221 # Signs the given data and returns the result. Mode 0 = binary signature
222 # mode 1 = ascii signature at end, mode 2 = ascii signature only
225 my $srcfile = &transname();
226 &write_entire_file($srcfile, $_[0]);
227 my $dstfile = &transname();
230 $cmd = "$gpgpath --output $dstfile --default-key $_[2]->{'key'} --sign $srcfile";
233 $cmd = "$gpgpath --output $dstfile --default-key $_[2]->{'key'} --clearsign $srcfile";
236 $cmd = "$gpgpath --armor --output $dstfile --default-key $_[2]->{'key'} --detach-sig $srcfile";
239 my ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", $cmd);
240 &reset_environment();
241 my ($error, $seen_pass);
242 my $pass = &get_passphrase($_[2]);
243 if (!defined($pass)) {
244 return $text{'gnupg_esignpass'}.". ".
245 &text('gnupg_canset', "/gnupg/edit_key.cgi?key=$_[2]->{'key'}").".";
248 my $rv = &wait_for($fh, "passphrase:", "failed", "error");
250 last if ($seen_pass++);
252 syswrite($fh, "$pass\n", length("$pass\n"));
264 my $dst = &read_entire_file($dstfile);
266 if ($error || $seen_pass > 1) {
267 return "<pre>$wait_for_input</pre>";
275 # verify_data(data, [signature])
276 # Verifies the signature on some data, and returns a status code and a message
277 # code 0 = verified successfully, message contains signer
278 # code 1 = verified successfully but no trust chain, message contains signer
279 # code 2 = failed to verify, message contains signer
280 # code 3 = do not have signers public key, message contains ID
281 # code 4 = verification totally failed, message contains reason
284 my $datafile = &transname();
285 &write_entire_file($datafile, $_[0]);
289 $cmd = "$gpgpath --verify $datafile";
292 $sigfile = &transname();
293 &write_entire_file($sigfile, $_[1]);
294 $cmd = "$gpgpath --verify $sigfile $datafile";
296 #local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", $cmd);
299 #local $out = $wait_for_input;
301 my $out = &backquote_command("$cmd 2>&1 </dev/null");
302 &reset_environment();
304 unlink($sigfile) if ($sigfile);
305 if ($out =~ /BAD signature from "(.*)"/i) {
308 elsif ($out =~ /key ID (\S+).*\n.*not found/i) {
311 elsif ($out =~ /Good signature from "(.*)"/i) {
313 if ($out =~ /warning/) {
325 # read_entire_file(file)
329 open(FILE, $_[0]) || return undef;
330 while(read(FILE, $buf, 1024) > 0) {
337 # write_entire_file(file, data)
338 sub write_entire_file
341 &open_tempfile($fh, ">$_[0]");
342 &print_tempfile($fh, $_[1]);
343 &close_tempfile($fh);
346 # get_trust_level(&key)
347 # Returns the trust level of a key
351 my $cmd = "$gpgpath --edit-key \"$_[0]->{'name'}->[0]\"";
352 my ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", $cmd);
353 my $rv = &wait_for($fh, "trust:\\s+(.)", "command>");
356 $tr = $matches[1] eq "q" ? 1 : $matches[1] eq "n" ? 2 :
357 $matches[1] eq "m" ? 3 : $matches[1] eq "f" ? 4 : 0;
362 syswrite($fh, "quit\n", length("quit\n"));
364 &reset_environment();
369 # Delete one public or secret key
373 if ($key->{'secret'}) {
375 my $cmd = "$gpgpath --delete-secret-key \"$key->{'name'}->[0]\"";
376 my ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", $cmd);
377 &wait_for($fh, "\\?");
378 syswrite($fh, "y\n");
379 &wait_for($fh, "\\?");
380 syswrite($fh, "y\n");
383 &reset_environment();
386 my $cmd = "$gpgpath --delete-key \"$key->{'name'}->[0]\"";
387 my ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", $cmd);
388 &wait_for($fh, "\\?");
389 syswrite($fh, "y\n");
392 &reset_environment();
395 # default_email_address()
396 # Returns the current user's email address, or undef if not possible
397 sub default_email_address
399 if (&foreign_check("mailbox")) {
400 &foreign_require("mailbox", "mailbox-lib.pl");
401 my ($fromaddr) = &mailbox::split_addresses(
402 &mailbox::get_preferred_from_address());
404 return $fromaddr->[0];
411 # Imports a key by ID from the configured keyserver. Returns 0 on success,
412 # 1 on failure, 2 if there was no change, 3 if the import appeared to success
413 # but the key isn't visible.
417 my $out = &backquote_command(
418 "$gpgpath --keyserver ".quotemeta($config{'keyserver'}).
419 " --recv-key ".quotemeta($id)." 2>&1 </dev/null");
420 my @keys = &list_keys();
421 my ($key) = grep { lc($_->{'key'}) eq lc($id) } @keys;
423 return wantarray ? (1, $out) : 1;
425 elsif ($out =~ /not\s+changed/) {
426 return wantarray ? (2, $key) : 2;
438 # search_gpg_keys(word)
439 # Searches the configured keyserver for GPG keys matching some name or email
440 # address, and returns them as a list of hash refs
444 my $cmd = "$gpgpath --keyserver ".quotemeta($config{'keyserver'}).
445 " --search-keys ".quotemeta($word);
446 my ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", $cmd);
449 $wait_for_input = undef;
450 my $rv = &wait_for($fh, "N.ext, or Q.uit");
451 if ($rv < 0) { last; }
454 foreach my $l (split(/\r?\n/, $wait_for_input)) {
455 if ($l =~ /^\(\d+\)\s+(\d+)\s+bit\s+(\S+)\s+key\s+(\S+)/) {
456 # Key with no name .. skip!
458 elsif ($l =~ /^\(\d+\)\s+(\S.*)\s+<(\S+)>/ ||
459 $l =~ /^\(\d+\)\s+(\S.*)/) {
460 # First name and email for a key
461 $key = { 'name' => [ $1 ],
463 $key->{'name'} =~ s/\s+$//;
467 elsif ($l =~ /^\s+(\S.*)\s+<(\S+)>/ && $key) {
468 # Additional name and email
469 push(@{$key->{'name'}}, $1);
470 push(@{$key->{'email'}}, $2);
472 elsif ($l =~ /\s+(\d+)\s+bit\s+(\S+)\s+key\s+(\S+),\s+created:\s+(\S+)/ && $key) {
477 if ($l =~ /revoked/) {
478 $key->{'revoked'} = 1;
484 &sysprint($fh, "N\n");