0d851678a7b4a0d9fbb9bdded2b4dee77ce21870
[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.4
13 #
14 # Copyright (C) 2008 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 335 2009-02-13 04:48:54Z 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$|) {
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 return &print_errors("[-] Encrypted hidden file");
415 }
416 }
417 }
418 return 1;
419 }
420
421 sub obf_skipped_hidden_files_dirs() {
422 @data_dir_files = ();
423 find(\&find_files, $data_dir);
424 for my $file (@data_dir_files) {
425 if ($file =~ m|^\.| or $file =~ m|/\.|) {
426 ### check for any .gpg or .asc extensions except
427 ### for the gpgdir_map_file
428 if ($file !~ m|gpgdir_map_file| and ($file =~ m|\.gpg$|
429 or $file =~ m|\.asc$|)) {
430 return &print_errors("[-] Encrypted hidden file");
431 }
432 }
433 }
434 return 1;
435 }
436
437
438 sub find_files() {
439 my $file = $File::Find::name;
440 push @data_dir_files, $file;
441 return;
442 }
443
444 sub collect_md5sums() {
445 @data_dir_files = ();
446 find(\&find_files, $data_dir);
447 for my $file (@data_dir_files) {
448 if (-f $file) {
449 $md5sums{$file} = md5_base64($file);
450 }
451 }
452 return 1;
453 }
454
455 sub md5sum_validation() {
456 @data_dir_files = ();
457 find(\&find_files, $data_dir);
458 for my $file (@data_dir_files) {
459 if (-f $file) {
460 if (not defined $md5sums{$file}
461 or $md5sums{$file} ne md5_base64($file)) {
462 return &print_errors("[-] MD5 sum mis-match for $file");
463 }
464 }
465 }
466 return 1;
467 }
468
469 sub test_mode() {
470 if (&run_cmd("$gpgdirCmd $default_args --test", $NO_APPEND)) {
471 my $found = 0;
472 open F, "< $current_test_file"
473 or die "[*] Could not open $current_test_file: $!";
474 while (<F>) {
475 if (/Decrypted\s+content\s+matches\s+original/i) {
476 $found = 1;
477 last;
478 }
479 }
480 close F;
481 return 1 if $found;
482 }
483 return &print_errors("[-] Encrypt/decrypt basic --test mode");
484 }
485
486 sub perl_compilation() {
487 unless (&run_cmd("perl -c $gpgdirCmd", $NO_APPEND)) {
488 return &print_errors("[-] $gpgdirCmd does not compile");
489 }
490 return 1;
491 }
492
493 sub getopt_test() {
494 if (&run_cmd("$gpgdirCmd --no-such-argument", $NO_APPEND)) {
495 return &print_errors("[-] $gpgdirCmd " .
496 "allowed --no-such-argument on the command line");
497 }
498 return 1;
499 }
500
501 sub dots_print() {
502 my $msg = shift;
503 &logr($msg);
504 my $dots = '';
505 for (my $i=length($msg); $i < $PRINT_LEN; $i++) {
506 $dots .= '.';
507 }
508 &logr($dots);
509 return;
510 }
511
512 sub print_errors() {
513 my $msg = shift;
514 &logr("fail ($test_num)\n$msg\n");
515 if (-e $current_test_file) {
516 &logr(" STDOUT and STDERR available in: " .
517 "$current_test_file file.\n");
518 open F, ">> $current_test_file"
519 or die "[*] Could not open $current_test_file: $!";
520 print F "MSG: $msg\n";
521 close F;
522 }
523 return 0;
524 }
525
526 sub run_cmd() {
527 my ($cmd, $append) = @_;
528
529 if ($append == $APPEND) {
530 open F, ">> $current_test_file"
531 or die "[*] Could not open $current_test_file: $!";
532 print F "CMD: $cmd\n";
533 close F;
534 } else {
535 open F, "> $current_test_file"
536 or die "[*] Could not open $current_test_file: $!";
537 print F "CMD: $cmd\n";
538 close F;
539 }
540 my $rv = ((system "$cmd >> $current_test_file 2>&1") >> 8);
541 if ($rv == 0) {
542 return 1;
543 }
544 return 0;
545 }
546
547 sub prepare_results() {
548 my $rv = 0;
549 die "[*] $output_dir does not exist" unless -d $output_dir;
550 die "[*] $logfile does not exist, has gpgdir_test.pl been executed?"
551 unless -e $logfile;
552 if (-e $tarfile) {
553 unlink $tarfile or die "[*] Could not unlink $tarfile: $!";
554 }
555
556 ### create tarball
557 system "tar cvfz $tarfile $logfile $output_dir";
558 print "[+] Test results file: $tarfile\n";
559 if (-e $tarfile) {
560 $rv = 1;
561 }
562 return $rv;
563 }
564
565 sub setup() {
566
567 $|++; ### turn off buffering
568
569 die "[*] $conf_dir directory does not exist." unless -d $conf_dir;
570 unless (-d $output_dir) {
571 mkdir $output_dir or die "[*] Could not mkdir $output_dir: $!";
572 }
573
574 die "[*] Password file $pw_file does not exist" unless -f $pw_file;
575 die "[*] Broken password file $broken_pw_file does not exist"
576 unless -f $broken_pw_file;
577 die "[*] $data_dir/multi-line-ascii file does not exist"
578 unless -f "$data_dir/multi-line-ascii";
579
580 for my $file (glob("$output_dir/cmd*")) {
581 unlink $file or die "[*] Could not unlink($file)";
582 }
583
584 for my $file (glob("$output_dir/*.test")) {
585 unlink $file or die "[*] Could not unlink($file)";
586 }
587
588 for my $file (glob("$output_dir/*.warn")) {
589 unlink $file or die "[*] Could not unlink($file)";
590 }
591
592 for my $file (glob("$output_dir/*.die")) {
593 unlink $file or die "[*] Could not unlink($file)";
594 }
595
596 die "[*] $gpgdirCmd does not exist" unless -e $gpgdirCmd;
597 die "[*] $gpgdirCmd not executable" unless -x $gpgdirCmd;
598
599 if (-e $logfile) {
600 unlink $logfile or die $!;
601 }
602 return;
603 }
604
605 sub pass() {
606 &logr("pass ($test_num)\n");
607 $successful_tests++;
608 return;
609 }
610
611 sub logr() {
612 my $msg = shift;
613
614 print STDOUT $msg;
615 open F, ">> $logfile" or die $!;
616 print F $msg;
617 close F;
618 return;
619 }