Imported new upstream release for gpgdir.
[pgp-tools.git] / gpgdir / test / gpgdir_test.pl
1 #!/usr/bin/perl -w
2 #
3 #############################################################################
4 #
5 # File: gpgdir_test.pl
6 #
7 # Purpose: This program provides a testing infrastructure for the gpgdir
8 # Single Packet Authorization client and server.
9 #
10 # Author: Michael Rash (mbr@cipherdyne.org)
11 #
12 # Version: 1.9.5
13 #
14 # Copyright (C) 2008-2009 Michael Rash (mbr@cipherdyne.org)
15 #
16 # License (GNU Public License):
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
26 # USA
27 #
28 #############################################################################
29 #
30 # $Id: gpgdir_test.pl 341 2009-08-26 02:43:51Z mbr $
31 #
32
33 use Digest::MD5 'md5_base64';
34 use File::Find;
35 use File::Copy;
36 use Getopt::Long;
37 use strict;
38
39 #=================== config defaults ==============
40 my $gpgdirCmd = '../gpgdir';
41
42 my $conf_dir = 'conf';
43 my $output_dir = 'output';
44 my $logfile = 'test.log';
45 my $tarfile = 'gpgdir_test.tar.gz';
46 my $data_dir = 'data-dir';
47
48 my $gpg_dir = "$conf_dir/test-gpg";
49 my $pw_file = "$conf_dir/test.pw";
50 my $broken_pw_file = "$conf_dir/broken.pw";
51 my $key_id = '375D7DB9';
52 #==================== end config ==================
53
54 my $help = 0;
55 my $test_num = 0;
56 my $PRINT_LEN = 68;
57 my $APPEND = 1;
58 my $NO_APPEND = 0;
59 my $failed_tests = 0;
60 my $prepare_results = 0;
61 my $successful_tests = 0;
62 my $current_test_file = "$output_dir/$test_num.test";
63 my $previous_test_file = '';
64 my @data_dir_files = ();
65 my %md5sums = ();
66
67 my $default_args = "--gnupg-dir $gpg_dir " .
68 "--Key-id $key_id --pw-file $pw_file";
69
70 die "[*] Use --help" unless GetOptions(
71 'Prepare-results' => \$prepare_results,
72 'help' => \$help
73 );
74
75 exit &prepare_results() if $prepare_results;
76
77 &setup();
78
79 &collect_md5sums();
80
81 &logr("\n[+] ==> Running gpgdir test suite <==\n\n");
82
83 ### execute the tests
84 &test_driver('(Setup) gpgdir program compilation', \&perl_compilation);
85 &test_driver('(Setup) Command line argument processing', \&getopt_test);
86 &test_driver('(Test mode) gpgdir basic test mode', \&test_mode);
87
88 ### encrypt/decrypt
89 &test_driver('(Encrypt dir) gpgdir directory encryption', \&encrypt);
90 &test_driver('(Encrypt dir) Files recursively encrypted',
91 \&recursively_encrypted);
92 &test_driver('(Encrypt dir) Exclude hidden files/dirs',
93 \&skipped_hidden_files_dirs);
94 &test_driver('(Decrypt dir) gpgdir directory decryption', \&decrypt);
95 &test_driver('(Decrypt dir) Files recursively decrypted',
96 \&recursively_decrypted);
97 &test_driver('(MD5 digest) match across encrypt/decrypt cycle',
98 \&md5sum_validation);
99
100 ### ascii encrypt/decrypt
101 &test_driver('(Ascii-armor dir) gpgdir directory encryption',
102 \&ascii_encrypt);
103 &test_driver('(Ascii-armor dir) Files recursively encrypted',
104 \&ascii_recursively_encrypted);
105 &test_driver('(Ascii-armor dir) Exclude hidden files/dirs',
106 \&skipped_hidden_files_dirs);
107 &test_driver('(Decrypt dir) gpgdir directory decryption', \&decrypt);
108 &test_driver('(Decrypt dir) Files recursively decrypted',
109 \&ascii_recursively_decrypted);
110 &test_driver('(MD5 digest) match across encrypt/decrypt cycle',
111 \&md5sum_validation);
112
113 ### obfuscate filenames encrypt/decrypt cycle
114 &test_driver('(Obfuscate filenames) gpgdir directory encryption',
115 \&obf_encrypt);
116 &test_driver('(Obfuscate filenames) Files recursively encrypted',
117 \&obf_recursively_encrypted);
118 &test_driver('(Obfuscate filenames) Exclude hidden files/dirs',
119 \&obf_skipped_hidden_files_dirs);
120 &test_driver('(Decrypt dir) gpgdir directory decryption',
121 \&obf_decrypt);
122 &test_driver('(Decrypt dir) Files recursively decrypted',
123 \&obf_recursively_decrypted); ### same as ascii_recursively_decrypted()
124 &test_driver('(MD5 digest) match across encrypt/decrypt cycle',
125 \&md5sum_validation);
126
127 ### sign/verify cycle
128 &test_driver('(Sign/verify dir) gpgdir directory signing', \&sign);
129 &test_driver('(Sign/verify dir) Files recursively signed',
130 \&recursively_signed);
131 &test_driver('(Sign/verify dir) Exclude hidden files/dirs',
132 \&skipped_hidden_files_dirs);
133 &test_driver('(Sign/verify dir) Broken signature detection',
134 \&broken_sig_detection);
135 &test_driver('(Sign/verify dir) gpgdir directory verification', \&verify);
136 &test_driver('(Sign/verify dir) Files recursively verified',
137 \&recursively_verified);
138
139 ### bad password detection
140 &test_driver('(Bad passphrase) detect broken passphrase',
141 \&broken_passphrase);
142
143 &logr("\n");
144 if ($successful_tests) {
145 &logr("[+] ==> Passed $successful_tests/$test_num tests " .
146 "against gpgdir. <==\n");
147 }
148 if ($failed_tests) {
149 &logr("[+] ==> Failed $failed_tests/$test_num tests " .
150 "against gpgdir. <==\n");
151 }
152 &logr("[+] This console output has been stored in: $logfile\n\n");
153
154 exit 0;
155 #======================== end main =========================
156
157 sub test_driver() {
158 my ($msg, $func_ref) = @_;
159
160 my $test_status = 'pass';
161 &dots_print($msg);
162 if (&{$func_ref}) {
163 &pass();
164 } else {
165 $test_status = 'fail';
166 $failed_tests++;
167 }
168
169 open C, ">> $current_test_file"
170 or die "[*] Could not open $current_test_file: $!";
171 print C "\nTEST: $msg, STATUS: $test_status\n";
172 close C;
173
174 $previous_test_file = $current_test_file;
175 $test_num++;
176 $current_test_file = "$output_dir/$test_num.test";
177 return;
178 }
179
180 sub broken_passphrase() {
181 if (not &run_cmd("$gpgdirCmd --gnupg-dir $gpg_dir " .
182 " --pw-file $broken_pw_file --Key-id $key_id -e $data_dir",
183 $NO_APPEND)) {
184 my $found_bad_pass = 0;
185 open F, "< $current_test_file" or die $!;
186 while (<F>) {
187 if (/BAD_?PASS/) {
188 $found_bad_pass = 1;
189 }
190 }
191 close F;
192 if ($found_bad_pass) {
193 return 1;
194 }
195 }
196 return &print_errors("[-] Accepted broken passphrase");
197 }
198
199 sub encrypt() {
200 if (&run_cmd("$gpgdirCmd $default_args -e $data_dir", $NO_APPEND)) {
201 return 1;
202 }
203 return &print_errors("[-] Directory encryption");
204 }
205
206 sub ascii_encrypt() {
207 if (&run_cmd("$gpgdirCmd $default_args --Plain-ascii -e $data_dir",
208 $NO_APPEND)) {
209 return 1;
210 }
211 return &print_errors("[-] Directory encryption");
212 }
213
214 sub obf_encrypt() {
215 if (&run_cmd("$gpgdirCmd $default_args -O -e $data_dir",
216 $NO_APPEND)) {
217 return 1;
218 }
219 return &print_errors("[-] Directory encryption");
220 }
221
222 sub sign() {
223 if (&run_cmd("$gpgdirCmd $default_args --sign $data_dir",
224 $NO_APPEND)) {
225 return 1;
226 }
227 return &print_errors("[-] Directory signing");
228 }
229
230 sub decrypt() {
231 if (&run_cmd("$gpgdirCmd $default_args -d $data_dir",
232 $NO_APPEND)) {
233 return 1;
234 }
235 return &print_errors("[-] Directory decryption");
236 }
237
238 sub obf_decrypt() {
239 if (&run_cmd("$gpgdirCmd $default_args -O -d $data_dir",
240 $NO_APPEND)) {
241 return 1;
242 }
243 return &print_errors("[-] Directory decryption");
244 }
245
246 sub verify() {
247 if (&run_cmd("$gpgdirCmd $default_args --verify $data_dir",
248 $NO_APPEND)) {
249 return 1;
250 }
251 return &print_errors("[-] Directory verification");
252 }
253
254 sub recursively_encrypted() {
255 @data_dir_files = ();
256 find(\&find_files, $data_dir);
257 for my $file (@data_dir_files) {
258 if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
259 unless ($file =~ m|\.gpg$|) {
260 return &print_errors("[-] File $file not encrypted");
261 }
262 }
263 }
264 return 1;
265 }
266
267 sub recursively_signed() {
268 @data_dir_files = ();
269 find(\&find_files, $data_dir);
270 for my $file (@data_dir_files) {
271 if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
272 if ($file !~ m|\.asc$|) {
273 unless (-e "$file.asc") {
274 return &print_errors("[-] File $file not signed");
275 }
276 }
277 }
278 }
279 return 1;
280 }
281
282 sub recursively_decrypted() {
283 @data_dir_files = ();
284 find(\&find_files, $data_dir);
285 for my $file (@data_dir_files) {
286 if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
287 if ($file =~ m|\.gpg$| or $file =~ m|\.pgp$|) {
288 return &print_errors("[-] File $file not encrypted");
289 }
290 }
291 }
292 return 1;
293 }
294
295 sub broken_sig_detection() {
296 move "$data_dir/multi-line-ascii", "$data_dir/multi-line-ascii.orig"
297 or die $!;
298 open F, "> $data_dir/multi-line-ascii" or die $!;
299 print F "bogus data\n";
300 close F;
301
302 &run_cmd("$gpgdirCmd $default_args --verify $data_dir",
303 $NO_APPEND);
304
305 my $found_bad_sig = 0;
306 open F, "< $current_test_file" or die $!;
307 while (<F>) {
308 if (/BADSIG/) {
309 $found_bad_sig = 1;
310 }
311 }
312 close F;
313
314 if ($found_bad_sig) {
315 unlink "$data_dir/multi-line-ascii";
316 move "$data_dir/multi-line-ascii.orig", "$data_dir/multi-line-ascii"
317 or die $!;
318 return 1;
319 }
320 return &print_errors("[-] Could not find bad signature");
321 }
322
323 sub recursively_verified() {
324
325 ### search for signature verification errors here
326 my $found_bad_sig = 0;
327 open F, "< $previous_test_file" or die $!;
328 while (<F>) {
329 if (/BADSIG/) {
330 $found_bad_sig = 1;
331 }
332 }
333 close F;
334
335 if ($found_bad_sig) {
336 return &print_errors("[-] Bad signature generated");
337 }
338
339 ### now remove signature files
340 @data_dir_files = ();
341 find(\&find_files, $data_dir);
342 for my $file (@data_dir_files) {
343 if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
344 if ($file =~ m|\.asc$|) {
345 unlink $file;
346 }
347 }
348 }
349 return 1;
350 }
351
352 sub ascii_recursively_encrypted() {
353 @data_dir_files = ();
354 find(\&find_files, $data_dir);
355 for my $file (@data_dir_files) {
356 if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
357 unless ($file =~ m|\.asc$|) {
358 return &print_errors("[-] File $file not encrypted");
359 }
360 }
361 }
362 return 1;
363 }
364
365 sub obf_recursively_encrypted() {
366 @data_dir_files = ();
367 find(\&find_files, $data_dir);
368 for my $file (@data_dir_files) {
369 if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
370 ### gpgdir_1.gpg
371 unless ($file =~ m|gpgdir_\d+\.gpg$|) {
372 return &print_errors("[-] File $file not " .
373 "encrypted and obfuscated");
374 }
375 }
376 }
377 return 1;
378 }
379
380 sub ascii_recursively_decrypted() {
381 @data_dir_files = ();
382 find(\&find_files, $data_dir);
383 for my $file (@data_dir_files) {
384 if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
385 if ($file =~ m|\.asc$|) {
386 return &print_errors("[-] File $file not encrypted");
387 }
388 }
389 }
390 return 1;
391 }
392
393 sub obf_recursively_decrypted() {
394 @data_dir_files = ();
395 find(\&find_files, $data_dir);
396 for my $file (@data_dir_files) {
397 if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
398 if ($file =~ m|\.asc$|) {
399 return &print_errors("[-] File $file not encrypted");
400 }
401 }
402 }
403 return 1;
404 }
405
406 sub skipped_hidden_files_dirs() {
407 @data_dir_files = ();
408 find(\&find_files, $data_dir);
409 for my $file (@data_dir_files) {
410 if ($file =~ m|^\.| or $file =~ m|/\.|) {
411 ### check for any .gpg or .asc extensions except
412 ### for the gpgdir_map_file
413 if ($file =~ m|\.gpg$| or $file =~ m|\.asc$|
414 or $file =~ m|\.pgp$|) {
415 return &print_errors("[-] Encrypted hidden file");
416 }
417 }
418 }
419 return 1;
420 }
421
422 sub obf_skipped_hidden_files_dirs() {
423 @data_dir_files = ();
424 find(\&find_files, $data_dir);
425 for my $file (@data_dir_files) {
426 if ($file =~ m|^\.| or $file =~ m|/\.|) {
427 ### check for any .gpg or .asc extensions except
428 ### for the gpgdir_map_file
429 if ($file !~ m|gpgdir_map_file| and ($file =~ m|\.gpg$|
430 or $file =~ m|\.asc$| or $file =~ m|\.pgp$|)) {
431 return &print_errors("[-] Encrypted hidden file");
432 }
433 }
434 }
435 return 1;
436 }
437
438
439 sub find_files() {
440 my $file = $File::Find::name;
441 push @data_dir_files, $file;
442 return;
443 }
444
445 sub collect_md5sums() {
446 @data_dir_files = ();
447 find(\&find_files, $data_dir);
448 for my $file (@data_dir_files) {
449 if (-f $file) {
450 $md5sums{$file} = md5_base64($file);
451 }
452 }
453 return 1;
454 }
455
456 sub md5sum_validation() {
457 @data_dir_files = ();
458 find(\&find_files, $data_dir);
459 for my $file (@data_dir_files) {
460 if (-f $file) {
461 if (not defined $md5sums{$file}
462 or $md5sums{$file} ne md5_base64($file)) {
463 return &print_errors("[-] MD5 sum mis-match for $file");
464 }
465 }
466 }
467 return 1;
468 }
469
470 sub test_mode() {
471 if (&run_cmd("$gpgdirCmd $default_args --test", $NO_APPEND)) {
472 my $found = 0;
473 open F, "< $current_test_file"
474 or die "[*] Could not open $current_test_file: $!";
475 while (<F>) {
476 if (/Decrypted\s+content\s+matches\s+original/i) {
477 $found = 1;
478 last;
479 }
480 }
481 close F;
482 return 1 if $found;
483 }
484 return &print_errors("[-] Encrypt/decrypt basic --test mode");
485 }
486
487 sub perl_compilation() {
488 unless (&run_cmd("perl -c $gpgdirCmd", $NO_APPEND)) {
489 return &print_errors("[-] $gpgdirCmd does not compile");
490 }
491 return 1;
492 }
493
494 sub getopt_test() {
495 if (&run_cmd("$gpgdirCmd --no-such-argument", $NO_APPEND)) {
496 return &print_errors("[-] $gpgdirCmd " .
497 "allowed --no-such-argument on the command line");
498 }
499 return 1;
500 }
501
502 sub dots_print() {
503 my $msg = shift;
504 &logr($msg);
505 my $dots = '';
506 for (my $i=length($msg); $i < $PRINT_LEN; $i++) {
507 $dots .= '.';
508 }
509 &logr($dots);
510 return;
511 }
512
513 sub print_errors() {
514 my $msg = shift;
515 &logr("fail ($test_num)\n$msg\n");
516 if (-e $current_test_file) {
517 &logr(" STDOUT and STDERR available in: " .
518 "$current_test_file file.\n");
519 open F, ">> $current_test_file"
520 or die "[*] Could not open $current_test_file: $!";
521 print F "MSG: $msg\n";
522 close F;
523 }
524 return 0;
525 }
526
527 sub run_cmd() {
528 my ($cmd, $append) = @_;
529
530 if ($append == $APPEND) {
531 open F, ">> $current_test_file"
532 or die "[*] Could not open $current_test_file: $!";
533 print F "CMD: $cmd\n";
534 close F;
535 } else {
536 open F, "> $current_test_file"
537 or die "[*] Could not open $current_test_file: $!";
538 print F "CMD: $cmd\n";
539 close F;
540 }
541 my $rv = ((system "$cmd >> $current_test_file 2>&1") >> 8);
542 if ($rv == 0) {
543 return 1;
544 }
545 return 0;
546 }
547
548 sub prepare_results() {
549 my $rv = 0;
550 die "[*] $output_dir does not exist" unless -d $output_dir;
551 die "[*] $logfile does not exist, has gpgdir_test.pl been executed?"
552 unless -e $logfile;
553 if (-e $tarfile) {
554 unlink $tarfile or die "[*] Could not unlink $tarfile: $!";
555 }
556
557 ### create tarball
558 system "tar cvfz $tarfile $logfile $output_dir";
559 print "[+] Test results file: $tarfile\n";
560 if (-e $tarfile) {
561 $rv = 1;
562 }
563 return $rv;
564 }
565
566 sub setup() {
567
568 $|++; ### turn off buffering
569
570 die "[*] $conf_dir directory does not exist." unless -d $conf_dir;
571 unless (-d $output_dir) {
572 mkdir $output_dir or die "[*] Could not mkdir $output_dir: $!";
573 }
574
575 die "[*] Password file $pw_file does not exist" unless -f $pw_file;
576 die "[*] Broken password file $broken_pw_file does not exist"
577 unless -f $broken_pw_file;
578 die "[*] $data_dir/multi-line-ascii file does not exist"
579 unless -f "$data_dir/multi-line-ascii";
580
581 for my $file (glob("$output_dir/cmd*")) {
582 unlink $file or die "[*] Could not unlink($file)";
583 }
584
585 for my $file (glob("$output_dir/*.test")) {
586 unlink $file or die "[*] Could not unlink($file)";
587 }
588
589 for my $file (glob("$output_dir/*.warn")) {
590 unlink $file or die "[*] Could not unlink($file)";
591 }
592
593 for my $file (glob("$output_dir/*.die")) {
594 unlink $file or die "[*] Could not unlink($file)";
595 }
596
597 die "[*] $gpgdirCmd does not exist" unless -e $gpgdirCmd;
598 die "[*] $gpgdirCmd not executable" unless -x $gpgdirCmd;
599
600 if (-e $logfile) {
601 unlink $logfile or die $!;
602 }
603 return;
604 }
605
606 sub pass() {
607 &logr("pass ($test_num)\n");
608 $successful_tests++;
609 return;
610 }
611
612 sub logr() {
613 my $msg = shift;
614
615 print STDOUT $msg;
616 open F, ">> $logfile" or die $!;
617 print F $msg;
618 close F;
619 return;
620 }