1#!/usr/bin/perl
2#
3# FILE:		sha2test.pl
4# AUTHOR:	Aaron D. Gifford - http://www.aarongifford.com/
5#
6# Copyright (c) 2001, Aaron D. Gifford
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12# 1. Redistributions of source code must retain the above copyright
13#    notice, this list of conditions and the following disclaimer.
14# 2. Redistributions in binary form must reproduce the above copyright
15#    notice, this list of conditions and the following disclaimer in the
16#    documentation and/or other materials provided with the distribution.
17# 3. Neither the name of the copyright holder nor the names of contributors
18#    may be used to endorse or promote products derived from this software
19#    without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
22# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE
25# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31# SUCH DAMAGE.
32#
33# $Id: sha2test.pl,v 1.1 2001/11/08 00:02:37 adg Exp adg $
34#
35
36sub usage {
37	my ($err) = shift(@_);
38
39	print <<EOM;
40Error:
41	$err
42Usage:
43	$0 [<options>] [<test-vector-info-file> [<test-vector-info-file> ...]]
44
45Options:
46	-256	Use SHA-256 hashes during testing
47	-384	Use SHA-384 hashes during testing
48	-512	Use SHA-512 hashes during testing
49	-ALL	Use all three hashes during testing
50	-c256 <command-spec>	Specify a command to execute to generate a
51				SHA-256 hash.  Be sure to include a '%'
52				character which will be replaced by the
53				test vector data filename containing the
54				data to be hashed.  This command implies
55				the -256 option.
56	-c384 <command-spec>	Specify a command to execute to generate a
57				SHA-384 hash.  See above.  Implies -384.
58	-c512 <command-spec>	Specify a command to execute to generate a
59				SHA-512 hash.  See above.  Implies -512.
60	-cALL <command-spec>	Specify a command to execute that will
61				generate all three hashes at once and output
62				the data in hexadecimal.  See above for
63				information about the <command-spec>.
64				This option implies the -ALL option, and
65				also overrides any other command options if
66				present.
67
68By default, this program expects to execute the command ./sha2 within the
69current working directory to generate all hashes.  If no test vector
70information files are specified, this program expects to read a series of
71files ending in ".info" within a subdirectory of the current working
72directory called "testvectors".
73
74EOM
75	exit(-1);
76}
77
78$c256 = $c384 = $c512 = $cALL = "";
79$hashes = 0;
80@FILES = ();
81
82# Read all command-line options and files:
83while ($opt = shift(@ARGV)) {
84	if ($opt =~ s/^\-//) {
85		if ($opt eq "256") {
86			$hashes |= 1;
87		} elsif ($opt eq "384") {
88			$hashes |= 2;
89		} elsif ($opt eq "512") {
90			$hashes |= 4;
91		} elsif ($opt =~ /^ALL$/i) {
92			$hashes = 7;
93		} elsif ($opt =~ /^c256$/i) {
94			$hashes |= 1;
95			$opt = $c256 = shift(@ARGV);
96			$opt =~ s/\s+.*$//;
97			if (!$c256 || $c256 !~ /\%/ || !-x $opt) {
98				usage("Missing or invalid command specification for option -c256: $opt\n");
99			}
100		} elsif ($opt =~ /^c384$/i) {
101			$hashes |= 2;
102			$opt = $c384 = shift(@ARGV);
103			$opt =~ s/\s+.*$//;
104			if (!$c384 || $c384 !~ /\%/ || !-x $opt) {
105				usage("Missing or invalid command specification for option -c384: $opt\n");
106			}
107		} elsif ($opt =~ /^c512$/i) {
108			$hashes |= 4;
109			$opt = $c512 = shift(@ARGV);
110			$opt =~ s/\s+.*$//;
111			if (!$c512 || $c512 !~ /\%/ || !-x $opt) {
112				usage("Missing or invalid command specification for option -c512: $opt\n");
113			}
114		} elsif ($opt =~ /^cALL$/i) {
115			$hashes = 7;
116			$opt = $cALL = shift(@ARGV);
117			$opt =~ s/\s+.*$//;
118			if (!$cALL || $cALL !~ /\%/ || !-x $opt) {
119				usage("Missing or invalid command specification for option -cALL: $opt\n");
120			}
121		} else {
122			usage("Unknown/invalid option '$opt'\n");
123		}
124	} else {
125		usage("Invalid, nonexistent, or unreadable file '$opt': $!\n") if (!-f $opt);
126		push(@FILES, $opt);
127	}
128}
129
130# Set up defaults:
131if (!$cALL && !$c256 && !$c384 && !$c512) {
132	$cALL = "./sha2 -ALL %";
133	usage("Required ./sha2 binary executable not found.\n") if (!-x "./sha2");
134}
135$hashes = 7 if (!$hashes);
136
137# Do some sanity checks:
138usage("No command was supplied to generate SHA-256 hashes.\n") if ($hashes & 1 == 1 && !$cALL && !$c256);
139usage("No command was supplied to generate SHA-384 hashes.\n") if ($hashes & 2 == 2 && !$cALL && !$c384);
140usage("No command was supplied to generate SHA-512 hashes.\n") if ($hashes & 4 == 4 && !$cALL && !$c512);
141
142# Default .info files:
143if (scalar(@FILES) < 1) {
144	opendir(DIR, "testvectors") || usage("Unable to scan directory 'testvectors' for vector information files: $!\n");
145	@FILES = grep(/\.info$/, readdir(DIR));
146	closedir(DIR);
147	@FILES = map { s/^/testvectors\//; $_; } @FILES;
148	@FILES = sort(@FILES);
149}
150
151# Now read in each test vector information file:
152foreach $file (@FILES) {
153	$dir = $file;
154	if ($file !~ /\//) {
155		$dir = "./";
156	} else {
157		$dir =~ s/\/[^\/]+$//;
158		$dir .= "/";
159	}
160	open(FILE, "<" . $file) ||
161		usage("Unable to open test vector information file '$file' for reading: $!\n");
162	$vec = { desc => "", file => "", sha256 => "", sha384 => "", sha512 => "" };
163	$data = $field = "";
164	$line = 0;
165	while(<FILE>) {
166		$line++;
167		s/\s*[\r\n]+$//;
168		next if ($field && $field ne "DESCRIPTION" && !$_);
169		if (/^(DESCRIPTION|FILE|SHA256|SHA384|SHA512):$/) {
170			if ($field eq "DESCRIPTION") {
171				$vec->{desc} = $data;
172			} elsif ($field eq "FILE") {
173				$data = $dir . $data if ($data !~ /^\//);
174				$vec->{file} = $data;
175			} elsif ($field eq "SHA256") {
176				$vec->{sha256} = $data;
177			} elsif ($field eq "SHA384") {
178				$vec->{sha384} = $data;
179			} elsif ($field eq "SHA512") {
180				$vec->{sha512} = $data;
181			}
182			$data = "";
183			$field = $1;
184		} elsif ($field eq "DESCRIPTION") {
185			s/^    //;
186			$data .= $_ . "\n";
187		} elsif ($field =~ /^SHA\d\d\d$/) {
188			s/^\s+//;
189			if (!/^([a-f0-9]{32}|[a-f0-9]{64})$/) {
190				usage("Invalid SHA-256/384/512 test vector information " .
191				      "file format at line $line of file '$file'\n");
192			}
193			$data .= $_;
194		} elsif ($field eq "FILE") {
195			s/^    //;
196			$data .= $_;
197		} else {
198			usage("Invalid SHA-256/384/512 test vector information file " .
199			      "format at line $line of file '$file'\n");
200		}
201	}
202	if ($field eq "DESCRIPTION") {
203		$data = $dir . $data if ($data !~ /^\//);
204		$vec->{desc} = $data;
205	} elsif ($field eq "FILE") {
206		$vec->{file} = $data;
207	} elsif ($field eq "SHA256") {
208		$vec->{sha256} = $data;
209	} elsif ($field eq "SHA384") {
210		$vec->{sha384} = $data;
211	} elsif ($field eq "SHA512") {
212		$vec->{sha512} = $data;
213	} else {
214		usage("Invalid SHA-256/384/512 test vector information file " .
215		      "format.  Missing required fields in file '$file'\n");
216	}
217
218	# Sanity check all entries:
219	if (!$vec->{desc}) {
220		usage("Invalid SHA-256/384/512 test vector information file " .
221		      "format.  Missing required DESCRIPTION field in file '$file'\n");
222	}
223	if (!$vec->{file}) {
224		usage("Invalid SHA-256/384/512 test vector information file " .
225		      "format.  Missing required FILE field in file '$file'\n");
226	}
227	if (! -f $vec->{file}) {
228		usage("The test vector data file (field FILE) name " .
229		      "'$vec->{file}' is not a readable file.  Check the FILE filed in " .
230		      "file '$file'.\n");
231	}
232	if (!($vec->{sha256} || $vec->{sha384} || $vec->{sha512})) {
233		usage("Invalid SHA-256/384/512 test vector information file " .
234		      "format.  There must be at least one SHA256, SHA384, or SHA512 " .
235		      "field specified in file '$file'.\n");
236	}
237	if ($vec->{sha256} !~ /^(|[a-f0-9]{64})$/) {
238		usage("Invalid SHA-256/384/512 test vector information file " .
239		      "format.  The SHA256 field is invalid in file '$file'.\n");
240	}
241	if ($vec->{sha384} !~ /^(|[a-f0-9]{96})$/) {
242		usage("Invalid SHA-256/384/512 test vector information file " .
243		      "format.  The SHA384 field is invalid in file '$file'.\n");
244	}
245	if ($vec->{sha512} !~ /^(|[a-f0-9]{128})$/) {
246		usage("Invalid SHA-256/384/512 test vector information file " .
247		      "format.  The SHA512 field is invalid in file '$file'.\n");
248	}
249	close(FILE);
250	if ($hashes & (($vec->{sha256} ? 1 : 0) | ($vec->{sha384} ? 2 : 0) | ($vec->{sha512} ? 4 : 0))) {
251		push(@VECTORS, $vec);
252	}
253}
254
255usage("There were no test vectors for the specified hash(es) in any of the test vector information files you specified.\n") if (scalar(@VECTORS) < 1);
256
257$num = $errors = $error256 = $error384 = $error512 = $tests = $test256 = $test384 = $test512 = 0;
258foreach $vec (@VECTORS) {
259	$num++;
260	print "TEST VECTOR #$num:\n";
261	print "\t" . join("\n\t", split(/\n/, $vec->{desc})) . "\n";
262	print "VECTOR DATA FILE:\n\t$vec->{file}\n";
263	$sha256 = $sha384 = $sha512 = "";
264	if ($cALL) {
265		$prog = $cALL;
266		$prog =~ s/\%/'$vec->{file}'/g;
267		@SHA = grep(/[a-fA-f0-9]{64,128}/, split(/\n/, `$prog`));
268		($sha256) = grep(/(^[a-fA-F0-9]{64}$|^[a-fA-F0-9]{64}[^a-fA-F0-9]|[^a-fA-F0-9][a-fA-F0-9]{64}$|[^a-fA-F0-9][a-fA-F0-9]{64}[^a-fA-F0-9])/, @SHA);
269		($sha384) = grep(/(^[a-fA-F0-9]{96}$|^[a-fA-F0-9]{96}[^a-fA-F0-9]|[^a-fA-F0-9][a-fA-F0-9]{96}$|[^a-fA-F0-9][a-fA-F0-9]{96}[^a-fA-F0-9])/, @SHA);
270		($sha512) = grep(/(^[a-fA-F0-9]{128}$|^[a-fA-F0-9]{128}[^a-fA-F0-9]|[^a-fA-F0-9][a-fA-F0-9]{128}$|[^a-fA-F0-9][a-fA-F0-9]{128}[^a-fA-F0-9])/, @SHA);
271	} else {
272		if ($c256) {
273			$prog = $c256;
274			$prog =~ s/\%/'$vec->{file}'/g;
275			@SHA = grep(/[a-fA-f0-9]{64,128}/, split(/\n/, `$prog`));
276			($sha256) = grep(/(^[a-fA-F0-9]{64}$|^[a-fA-F0-9]{64}[^a-fA-F0-9]|[^a-fA-F0-9][a-fA-F0-9]{64}$|[^a-fA-F0-9][a-fA-F0-9]{64}[^a-fA-F0-9])/, @SHA);
277		}
278		if ($c384) {
279			$prog = $c384;
280			$prog =~ s/\%/'$vec->{file}'/g;
281			@SHA = grep(/[a-fA-f0-9]{64,128}/, split(/\n/, `$prog`));
282			($sha384) = grep(/(^[a-fA-F0-9]{96}$|^[a-fA-F0-9]{96}[^a-fA-F0-9]|[^a-fA-F0-9][a-fA-F0-9]{96}$|[^a-fA-F0-9][a-fA-F0-9]{96}[^a-fA-F0-9])/, @SHA);
283		}
284		if ($c512) {
285			$prog = $c512;
286			$prog =~ s/\%/'$vec->{file}'/g;
287			@SHA = grep(/[a-fA-f0-9]{64,128}/, split(/\n/, `$prog`));
288			($sha512) = grep(/(^[a-fA-F0-9]{128}$|^[a-fA-F0-9]{128}[^a-fA-F0-9]|[^a-fA-F0-9][a-fA-F0-9]{128}$|[^a-fA-F0-9][a-fA-F0-9]{128}[^a-fA-F0-9])/, @SHA);
289		}
290	}
291	usage("Unable to generate any hashes for file '$vec->{file}'!\n") if (!$sha256 && !$sha384 && $sha512);
292	$sha256 =~ tr/A-F/a-f/;
293	$sha384 =~ tr/A-F/a-f/;
294	$sha512 =~ tr/A-F/a-f/;
295	$sha256 =~ s/^.*([a-f0-9]{64}).*$/$1/;
296	$sha384 =~ s/^.*([a-f0-9]{96}).*$/$1/;
297	$sha512 =~ s/^.*([a-f0-9]{128}).*$/$1/;
298
299	if ($sha256 && $hashes & 1 == 1) {
300		if ($vec->{sha256} eq $sha256) {
301			print "SHA256 MATCHES:\n\t$sha256\n"
302		} else {
303			print "SHA256 DOES NOT MATCH:\n\tEXPECTED:\n\t\t$vec->{sha256}\n" .
304			      "\tGOT:\n\t\t$sha256\n\n";
305			$error256++;
306		}
307		$test256++;
308	}
309	if ($sha384 && $hashes & 2 == 2) {
310		if ($vec->{sha384} eq $sha384) {
311			print "SHA384 MATCHES:\n\t" . substr($sha384, 0, 64) . "\n\t" .
312			      substr($sha384, -32) . "\n";
313		} else {
314			print "SHA384 DOES NOT MATCH:\n\tEXPECTED:\n\t\t" .
315			      substr($vec->{sha384}, 0, 64) . "\n\t\t" .
316			      substr($vec->{sha384}, -32) . "\n\tGOT:\n\t\t" .
317			      substr($sha384, 0, 64) . "\n\t\t" . substr($sha384, -32) . "\n\n";
318			$error384++;
319		}
320		$test384++;
321	}
322	if ($sha512 && $hashes & 4 == 4) {
323		if ($vec->{sha512} eq $sha512) {
324			print "SHA512 MATCHES:\n\t" . substr($sha512, 0, 64) . "\n\t" .
325			      substr($sha512, -64) . "\n";
326		} else {
327			print "SHA512 DOES NOT MATCH:\n\tEXPECTED:\n\t\t" .
328			      substr($vec->{sha512}, 0, 64) . "\n\t\t" .
329			      substr($vec->{sha512}, -32) . "\n\tGOT:\n\t\t" .
330			      substr($sha512, 0, 64) . "\n\t\t" . substr($sha512, -64) . "\n\n";
331			$error512++;
332		}
333		$test512++;
334	}
335}
336
337$errors = $error256 + $error384 + $error512;
338$tests = $test256 + $test384 + $test512;
339print "\n\n===== RESULTS ($num VECTOR DATA FILES HASHED) =====\n\n";
340print "HASH TYPE\tNO. OF TESTS\tPASSED\tFAILED\n";
341print "---------\t------------\t------\t------\n";
342if ($test256) {
343	$pass = $test256 - $error256;
344	print "SHA-256\t\t".substr("           $test256", -12)."\t".substr("     $pass", -6)."\t".substr("     $error256", -6)."\n";
345}
346if ($test384) {
347	$pass = $test384 - $error384;
348	print "SHA-384\t\t".substr("           $test384", -12)."\t".substr("     $pass", -6)."\t".substr("     $error384", -6)."\n";
349}
350if ($test512) {
351	$pass = $test512 - $error512;
352	print "SHA-512\t\t".substr("           $test512", -12)."\t".substr("     $pass", -6)."\t".substr("     $error512", -6)."\n";
353}
354print "----------------------------------------------\n";
355$pass = $tests - $errors;
356print "TOTAL:          ".substr("           $tests", -12)."\t".substr("     $pass", -6)."\t".substr("     $errors", -6)."\n\n";
357print "NO ERRORS!  ALL TESTS WERE SUCCESSFUL!\n\n" if (!$errors);
358
359