source: trunk/scripts/samhainadmin-sig.pl.in@ 558

Last change on this file since 558 was 550, checked in by katerina, 5 years ago

Fix for ticket #442 (support for OpenBSD signify).

  • Property svn:executable set to *
File size: 16.5 KB
Line 
1#! /usr/bin/perl
2
3# Copyright Rainer Wichmann (2004)
4#
5# License Information:
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19#
20
21use warnings;
22use strict;
23use Getopt::Long;
24use File::Basename;
25use File::Copy;
26use File::stat;
27use File::Temp qw/ tempfile tempdir unlink0 /;
28use IO::Handle;
29use Fcntl qw(:DEFAULT :flock);
30use Tie::File;
31
32# Do I/O to the data file in binary mode (so it
33# wouldn't complain about invalid UTF-8 characters).
34use bytes;
35
36File::Temp->safe_level( File::Temp::HIGH );
37
38my %opts = ();
39my $action;
40my $file1;
41my $file2;
42my $passphrase;
43my $secretkey;
44my $return_from_sign = 0;
45my $no_print_examine = 0;
46my $no_remove_lock = 0;
47my $base = basename($0);
48
49my $cfgfile = "@myconffile@";
50my $datafile = "@mydatafile@";
51my $daemon = "@sbindir@/@install_name@";
52my $signify = "@mysignify@";
53
54my $SIGDIR = "$ENV{'HOME'}/.signify";
55my $KEYID = "@install_name@";
56
57$cfgfile =~ s/^REQ_FROM_SERVER//;
58$datafile =~ s/^REQ_FROM_SERVER//;
59
60$signify = "signify-openbsd" if ($signify eq "");
61
62sub usage() {
63 print "Usage:\n";
64 print " $base { -m F | --create-cfgfile } [options] [in.cfgfile]\n";
65 print " Sign the configuration file. If in.cfgfile is given, sign it\n";
66 print " and install it as configuration file.\n\n";
67
68 print " $base { -m f | --print-cfgfile } [options] \n";
69 print " Print the configuration file to stdout. Signatures are removed.\n\n";
70
71 print " $base { -m D | --create-datafile } [options] [in.datafile]\n";
72 print " Sign the database file. If in.datafile is given, sign it\n";
73 print " and install it as database file.\n\n";
74
75 print " $base { -m d | --print-datafile } [options] \n";
76 print " Print the database file to stdout. Signatures are removed. Use\n";
77 print " option --list to list files in database rather than printing the raw file.\n\n";
78
79 print " $base { -m R | --remove-signature } [options] file1 [file2 ...]\n";
80 print " Remove cleartext signature from input file(s). The file\n";
81 print " is replaced by the non-signed file.\n\n";
82
83 print " $base { -m E | --sign } [options] file1 [file2 ...]\n";
84 print " Sign file(s) with a cleartext signature. The file\n";
85 print " is replaced by the signed file.\n\n";
86
87 print " $base { -m e | --examine } [options] file1 [file2 ...]\n";
88 print " Report signature status of file(s).\n\n";
89
90 print " $base { -m G | --generate-keys } [options] \n";
91 print " Generate a signify keypair to use for signing.\n\n";
92
93 print "Options:\n";
94 print " -c cfgfile --cfgfile cfgfile\n";
95 print " Select an alternate configuration file.\n\n";
96
97 print " -d datafile --datafile datafile\n";
98 print " Select an alternate database file.\n\n";
99
100 print " -p passphrase --passphrase passphrase\n";
101 print " Set the passphrase for signify. By default, signify will ask.\n\n";
102
103 print " -s signify_dir --signify-dir signify_dir\n";
104 print " Select an alternate directory to locate the secret keyring.\n";
105 print " Will use '$ENV{'HOME'}/.signify/' by default.\n\n";
106
107 print " -k keyid --keyid keyid\n";
108 print " Select the keyid to use for signing.\n\n";
109
110 print " -l --list\n";
111 print " List the files in database rather than printing the raw file.\n\n";
112
113 print " -v --verbose\n";
114 print " Verbose output.\n\n";
115 return;
116}
117
118sub check_signify_uid () {
119 if (0 != $>) {
120 print "--------------------------------------------------\n";
121 print "\n";
122 print " You are not root. Please remember that samhain/yule\n";
123 print " will use the public key of root to verify a signature.\n";
124 print "\n";
125 print "--------------------------------------------------\n";
126 } else {
127 if (!("@yulectl_prg@" =~ //)) {
128 print "--------------------------------------------------\n";
129 print "\n";
130 print " Please remember that yule will drop root after startup. Signature\n";
131 print " verification on SIGHUP will fail if you do not import the public key\n";
132 print " into the ~/.signify/ directory of the non-root yule user.\n";
133 print "\n";
134 print "--------------------------------------------------\n";
135 }
136 }
137}
138
139sub check_signify_sign () {
140 if ( defined($secretkey)) {
141 if ( (!-d "$secretkey")){
142 print "--------------------------------------------------\n";
143 print "\n";
144 print " Secret key $secretkey not found!\n";
145 print "\n";
146 print " Please check the path/name of the alternate secret key.\n";
147 print "\n";
148 print "--------------------------------------------------\n";
149 print "\n";
150 exit;
151 }
152 } else {
153 if ( (!-d "$SIGDIR") || (!-e "${SIGDIR}/${KEYID}.sec")) {
154 print "--------------------------------------------------\n";
155 print "\n";
156 if (!-d "$SIGDIR") {
157 print " Directory $SIGDIR not found!\n";
158 } else {
159 print " Secret key ${SIGDIR}/${KEYID}.sec not found!\n";
160 }
161 print "\n";
162 print " This indicates that you have never created a \n";
163 print " public/private keypair, and thus cannot sign.\n";
164 print " \n";
165 print " Please use $0 --generate-keys or\n";
166 print " $signify -G -s ${SIGDIR}/${KEYID}.sec -p ${SIGDIR}/${KEYID}.pub\n";
167 print " to generate a public/private keypair first.\n";
168 print "\n";
169 print "--------------------------------------------------\n";
170 print "\n";
171 exit;
172 }
173 }
174}
175
176sub check_signify_verify () {
177 if ( (!-d "${SIGDIR}") || (!-e "${SIGDIR}/${KEYID}.pub")) {
178 print "--------------------------------------------------\n";
179 print "\n";
180 if (!-d "$SIGDIR") {
181 print " Directory $SIGDIR not found!\n";
182 } else {
183 print " Public key ${SIGDIR}/${KEYID}.pub not found!\n";
184 }
185 print "\n";
186 print " This indicates that you have no public key\n";
187 print " to verify signatures.\n";
188 print " \n";
189 print " Please copy the public key ${KEYID}.pub of\n";
190 print " the user who is signing the configuration/database files\n";
191 print " into the directory $SIGDIR.\n";
192 print "\n";
193 print "--------------------------------------------------\n";
194 print "\n";
195 exit;
196 }
197}
198
199
200sub generate () {
201 my $command = "$signify -G -s ${SIGDIR}/${KEYID}.sec -p ${SIGDIR}/${KEYID}.pub";
202 if (!-d "${SIGDIR}") {
203 unless(mkdir "$SIGDIR", 0750) {
204 die "Creating directory $SIGDIR failed: $?";
205 }
206 }
207 check_signify_uid();
208 system ($command) == 0
209 or die "system $command failed: $?";
210 exit;
211}
212
213sub examine () {
214 my $iscfg = 0;
215 my $have_fp = 0;
216 my $have_sig = 0;
217 my $message = '';
218 my $retval = 9;
219 my $fh;
220 my $filename;
221
222 if (!($file1 =~ /^\-$/)) {
223 die ("Cannot open $file1 for read: $!") unless ((-e $file1) && (-r _));
224 }
225 open FIN, "<$file1" or die "Cannot open $file1 for read: $!";
226
227 my $dir = tempdir( CLEANUP => 1 );
228 $filename = $dir . "/exa_jhfdbilw." . $$;
229 open $fh, ">$filename" or die "Cannot open $filename";
230 autoflush $fh 1;
231
232 while (<FIN>) {
233 print $fh $_;
234 if ($_ =~ /^\s*\[Misc\]/) {
235 $iscfg = 1;
236 }
237 }
238 if ($iscfg == 1) {
239 $message .= "File $file1 is a configuration file\n\n";
240 } else {
241 $message .= "File $file1 is a database file\n\n";
242 }
243
244
245 my $command = "$signify -Vem /dev/null -p ${SIGDIR}/${KEYID}.pub ";
246 $command .= "-x $filename ";
247 if (defined($opts{'v'})) {
248 $command .= "2>&1";
249 } else {
250 $command .= "2>/dev/null";
251 }
252
253 print STDOUT "Using: $command\n\n" if (defined($opts{'v'}));
254 open SIGIN, "$command |" or die "Cannot fork: $!";
255
256 while (<SIGIN>) {
257 chomp ($_);
258 if ($_ =~ /^Signature Verified$/) {
259 $message .= "GOOD signature with key: ${SIGDIR}/${KEYID}.pub\n";
260 $have_sig = 1;
261 $retval = 0;
262 }
263 print STDOUT $_ if (defined($opts{'v'}));
264 }
265 close (SIGIN);
266 print STDOUT "\n" if (defined($opts{'v'}));
267 if ($have_sig == 0) {
268 $message .= "NO valid signature found\n";
269 }
270 close (FIN);
271 if ($no_print_examine == 0) {
272 print STDOUT $message;
273 }
274 unlink0( $fh, $filename ) or die "Cannot unlink $filename safely";
275 return $retval;
276}
277
278sub wstrip ($) {
279 $_ = shift;
280 $_ =~ s/\s+//g;
281 return $_;
282}
283
284sub remove () {
285 my $bodystart = 1;
286 my $sigstart = 0;
287 my $sigend = 0;
288 my $filename = "";
289 my $fh;
290 my $stats;
291
292 open FH, "<$file1" or die "Cannot open file $file1 for read: $!";
293 if (!($file1 =~ /^\-$/)) {
294 flock(FH, LOCK_EX) unless ($no_remove_lock == 1);
295 my $dir = tempdir( CLEANUP => 1 ) or die "Tempdir failed";
296 $filename = $dir . "/rem_iqegBCQb." . $$;
297 open $fh, ">$filename" or die "Cannot open $filename";
298 $stats = stat($file1);
299 } else {
300 open $fh, ">$file1" or die "Cannot open file $file1 for write: $!";
301 }
302 autoflush $fh 1;
303 while (<FH>) {
304 if ($_ =~ /^untrusted comment: /) {
305 $sigstart = 1;
306 $bodystart = 0;
307 next;
308 } elsif (($sigstart == 1) && (wstrip($_) =~ m{^(?: [A-Za-z0-9+/]{4} )*(?:[A-Za-z0-9+/]{2} [AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$}xm )) {
309 $sigstart = 0;
310 $bodystart = 1;
311 next;
312 } elsif (($sigstart == 1) && ($bodystart == 0)) {
313 # comment NOT followed by signature
314 $sigstart = 0;
315 next;
316 }
317
318 if ($bodystart == 1) {
319 print $fh $_;
320 }
321 }
322 if (!($file1 =~ /^\-$/)) {
323 copy("$filename", "$file1")
324 or die "Copy $filename to $file1 failed: $!";
325 chmod $stats->mode, $file1;
326 chown $stats->uid, $stats->gid, $file1;
327 flock(FH, LOCK_UN) unless ($no_remove_lock == 1);
328 close FH;
329 }
330 unlink0( $fh, $filename ) or die "Cannot unlink $filename safely";
331 return;
332}
333
334sub print_cfgfile () {
335 my $bodystart = 0;
336 my $sigstart = 0;
337
338 if (!defined($file2)) {
339 $file2 = '-';
340 }
341
342 open FH, "<$file1" or die "Cannot open file $file1 for read: $!";
343 open FO, ">$file2" or die "Cannot open file $file2 for write: $!";
344 while (<FH>) {
345 if ($_ =~ /^untrusted comment: /) {
346 $sigstart = 1;
347 $bodystart = 0;
348 next;
349 } elsif (($sigstart == 1) && (wstrip($_) =~ m{^(?: [A-Za-z0-9+/]{4} )*(?:[A-Za-z0-9+/]{2} [AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$}xm )) {
350 $sigstart = 0;
351 $bodystart = 1;
352 next;
353 } elsif (($sigstart == 1) && ($bodystart == 0)) {
354 # comment NOT followed by signature
355 $sigstart = 0;
356 next;
357 }
358 if ($bodystart == 1) {
359 print FO $_;
360 }
361 }
362 exit;
363}
364
365sub print_datafile () {
366 die ("Cannot find program $daemon")
367 unless (-e $daemon);
368 if (defined($opts{'v'})) {
369 open FH, "$daemon --full-detail -d $datafile |"
370 or die "Cannot open datafile $datafile for read: $!";
371 } else {
372 open FH, "$daemon -d $datafile |"
373 or die "Cannot open datafile $datafile for read: $!";
374 }
375 while (<FH>) {
376 print $_;
377 }
378 exit;
379}
380
381sub sign_file () {
382
383 my $fileout = '';
384 my $bodystart = 1;
385 my $sigstart = 0;
386 my $sigend = 0;
387 my $stats;
388 my $fh1;
389 my $filename1;
390 my $flag1 = 0;
391
392 check_signify_uid();
393
394 if (!defined($file2)) {
395 $file2 = $file1;
396 }
397
398 if ($file1 =~ /^\-$/) {
399 my $dir = tempdir( CLEANUP => 1 ) or die "Tempdir failed";
400 $filename1 = $dir . "/sig_vs8827sd." . $$;
401 open $fh1, ">$filename1" or die "Cannot open $filename1";
402 $flag1 = 1;
403 # my ($fh1, $filename1) = tempfile(UNLINK => 1);
404
405 while (<STDIN>) {
406 if ($_ =~ /^untrusted comment: /) {
407 $sigstart = 1;
408 $bodystart = 0;
409 next;
410 } elsif (($sigstart == 1) && (wstrip($_) =~ m{^(?: [A-Za-z0-9+/]{4} )*(?:[A-Za-z0-9+/]{2} [AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$}xm )) {
411 $sigstart = 0;
412 $bodystart = 1;
413 next;
414 } elsif (($sigstart == 1) && ($bodystart == 0)) {
415 #comment NOT followed by signature
416 $sigstart = 0;
417 next;
418 }
419
420 if ($bodystart == 1) {
421 print $fh1 $_;
422 }
423 }
424 $file1 = $filename1;
425 $fileout = '-';
426 } else {
427 open (LOCKFILE, "<$file1") or die "Cannot open $file1: $!";
428 flock(LOCKFILE, LOCK_EX);
429 $no_print_examine = 1;
430 $no_remove_lock = 1;
431 if (examine() < 2) {
432 remove();
433 }
434 $fileout = $file1 . ".sig";
435 $stats = stat($file1)
436 or die "No file $file1: $!";
437 }
438
439 my $command = "$signify -Se ";
440 $command .= "-s ${SIGDIR}/${KEYID}.sec ";
441 $command .= "-x ${fileout} ";
442 $command .= "-m $file1";
443
444 if (defined($passphrase)) {
445 local $SIG{PIPE} = 'IGNORE';
446 open (FH, "|$command") or die "can't fork: $!";
447 print FH "$passphrase" or die "can't write: $!";
448 close FH or die "can't close: status=$?";
449 } else {
450 system("$command") == 0
451 or die "system $command failed: $?";
452 }
453
454 if (!($fileout =~ /^\-$/)) {
455 my $st_old = stat($file1)
456 or die "No file $file1: $!";
457 my $st_new = stat($fileout)
458 or die "No file $fileout: $!";
459 die ("Signed file is smaller than unsigned file")
460 unless ($st_new->size > $st_old->size);
461 move("$fileout", "$file2")
462 or die "Move $fileout to $file2 failed: $!";
463 chmod $stats->mode, $file2;
464 chown $stats->uid, $stats->gid, $file2;
465 flock(LOCKFILE, LOCK_UN);
466 }
467
468 if ($flag1 == 1) {
469 unlink0( $fh1, $filename1 ) or die "Cannot unlink $filename1 safely";
470 }
471 if ($return_from_sign == 1) {
472 return;
473 }
474 exit;
475}
476
477Getopt::Long::Configure ("posix_default");
478Getopt::Long::Configure ("bundling");
479# Getopt::Long::Configure ("debug");
480
481GetOptions (\%opts, 'm=s', 'h|help', 'v|verbose', 'l|list',
482 'c|cfgfile=s',
483 'd|datafile=s',
484 'p|passphrase=s',
485 's|secretkey=s',
486 'k|keyid=s',
487 'create-cfgfile', # -m F
488 'print-cfgfile', # -m f
489 'create-datafile', # -m D
490 'print-datafile', # -m d
491 'remove-signature',# -m R
492 'sign', # -m E
493 'examine', # -m e
494 'generate-keys'); # -m G
495
496if (defined ($opts{'h'})) {
497 usage();
498 exit;
499}
500
501if (defined($opts{'k'})) {
502 $KEYID = $opts{'k'};
503}
504if (defined($opts{'c'})) {
505 $cfgfile = $opts{'c'};
506}
507if (defined($opts{'d'})) {
508 $datafile = $opts{'d'};
509}
510if (defined($opts{'p'})) {
511 $passphrase = $opts{'p'};
512}
513if (defined($opts{'s'})) {
514 $SIGDIR = $opts{'s'};
515}
516
517if (defined ($opts{'m'}) && ($opts{'m'} =~ /[FfDdREeG]{1}/) ) {
518 $action = $opts{'m'};
519}
520elsif (defined ($opts{'create-cfgfile'})) {
521 $action = 'F';
522}
523elsif (defined ($opts{'print-cfgfile'})) {
524 $action = 'f';
525}
526elsif (defined ($opts{'create-datafile'})) {
527 $action = 'D';
528}
529elsif (defined ($opts{'print-datafile'})) {
530 $action = 'd';
531}
532elsif (defined ($opts{'remove-signature'})) {
533 $action = 'R';
534}
535elsif (defined ($opts{'sign'})) {
536 $action = 'E';
537}
538elsif (defined ($opts{'examine'})) {
539 $action = 'e';
540}
541elsif (defined ($opts{'generate-keys'})) {
542 $action = 'G';
543}
544else {
545 usage();
546 die ("No valid action specified !");
547}
548
549if (defined($ARGV[0])) {
550 $file1 = $ARGV[0];
551}
552if (defined($ARGV[1])) {
553 $file2 = $ARGV[1];
554}
555
556
557if (($action =~ /[REe]{1}/) && !defined($file1)) {
558 usage();
559 die("Option -m $action requires a filename (or '-' for stdio)\n");
560}
561
562if ($action =~ /^F$/) {
563 if (!defined($file1)) {
564 $file1 = $cfgfile;
565 }
566 $file2 = $cfgfile;
567 sign_file ();
568}
569
570if ($action =~ /^D$/) {
571 if (!defined($file1)) {
572 $file1 = $datafile;
573 }
574 $file2 = $datafile;
575 sign_file ();
576}
577
578if ($action =~ /^R$/) {
579 # $file1 defined
580 my $i = 0;
581 while (defined($ARGV[$i])) {
582 $file1 = $ARGV[$i];
583 remove ();
584 ++$i;
585 }
586}
587
588if ($action =~ /^E$/) {
589 # $file1 defined
590 # default: $file2 = $file1
591 check_signify_sign();
592 my $i = 0;
593 while (defined($ARGV[$i])) {
594 $file1 = $ARGV[$i];
595 $file2 = $file1;
596 $return_from_sign = 1;
597 sign_file ();
598 ++$i;
599 }
600}
601
602if ($action =~ /^e$/) {
603 # $file1 defined
604 # default: $file2 = stdout
605 check_signify_verify();
606 my $i = 0;
607 my $ret = 0;
608 while (defined($ARGV[$i])) {
609 print "\n";
610 $file1 = $ARGV[$i];
611 $ret += examine ();
612 ++$i;
613 print "\n--------------------------------\n" if (defined($ARGV[$i]));
614 }
615 exit($ret);
616}
617
618if ($action =~ /^f$/) {
619 $file1 = $cfgfile;
620 $file2 = "-";
621 print_cfgfile ();
622}
623
624if ($action =~ /^d$/) {
625 # $file1 irrelevant
626 if (defined($opts{'l'})) {
627 print_datafile ();
628 } else {
629 $file1 = $datafile;
630 $file2 = "-";
631 print_cfgfile ();
632 }
633}
634
635
636
Note: See TracBrowser for help on using the repository browser.