3 # Debian/OpenSSL Weak Key Detector
5 # Written by Florian Weimer <fw@deneb.enyo.de>, with blacklist data
6 # from Kees Cook, Peter Palfrader and James Strandboge.
8 # Patches and comments are welcome. Please send them to
9 # <fw@deneb.enyo.de>, and use "dowkd" in the subject line.
16 usage: $0 [OPTIONS...] COMMAND [ARGUMENTS...]
20 file: examine files on the command line for weak keys
21 host: examine the specified hosts for weak SSH keys
22 user: examine user SSH keys for weakness; examine all users if no
24 help: show this help screen
28 -c FILE: set the database cache file name (default: dowkd.db)
30 dowkd currently handles the following OpenSSH host and user keys,
31 provided they have been generated on a little-endian architecture
32 (such as i386 or amd64): RSA/1024, RSA/2048 and DSA/1024. (The
33 OpenSSH version in Debian does not support DSA key generation with)
36 OpenVPN shared also detected on little-endian architecture.
38 Note that the blacklist by dowkd may be incomplete; it is only
39 intended as a quick check.
51 my $db_file = 'dowkd.db';
57 $db = tie
%db, 'DB_File', $db_file, O_RDWR
| O_CREAT
, 0777, $DB_BTREE
58 or die "error: could not open database: $!\n";
60 $db{''} = $db_version;
61 while (my $line = <DATA
>) {
62 next if $line =~ /^\**$/;
64 $line =~ /^[0-9a-f]{32}$/ or die "error: invalid data line";
65 $line =~ s/(..)/chr(hex($1))/ge;
74 $db = tie
%db, 'DB_File', $db_file, O_RDONLY
, 0777, $DB_BTREE
75 or die "error: could not open database: $!\n";
76 my $stored_version = $db{''};
77 $stored_version && $stored_version eq $db_version or create_db
;
84 sub safe_backtick
(@
) {
88 or die "error: failed to spawn $args[0]: $!\n";
94 @result = scalar(<$fh>);
97 $?
== 0 or return undef;
106 my $keys_vulnerable = 0;
109 print STDERR
"summary: keys found: $keys_found, weak keys: $keys_vulnerable\n";
112 sub check_hash
($$) {
113 my ($name, $hash) = @_;
115 if (exists $db{$hash}) {
117 print "$name: weak key\n";
121 sub ssh_fprint_file
($) {
123 my $data = safe_backtick qw
/ssh-keygen -l -f/, $name;
124 defined $data or return ();
125 my @data = $data =~ /^(\d+) ([0-9a-f]{2}(?::[0-9a-f]{2}){15})/;
126 return @data if @data == 2;
130 sub ssh_fprint_check
($$$) {
131 my ($name, $length, $hash) = @_;
132 if ($length == 1024 || $length == 2048) {
134 $hash =~ s/(..)/chr(hex($1))/ge;
135 check_hash
$name, $hash;
137 warn "$name: warning: no suitable blacklist\n";
141 sub from_ssh_key_file
($) {
143 my ($length, $hash) = ssh_fprint_file
$name;
144 if ($length && $hash) {
145 ssh_fprint_check
"$name:1", $length, $hash;
147 warn "$name:1: warning: failed to parse SSH key file\n";
153 seek $tmp, 0, 0 or die "seek: $!";
154 truncate $tmp, 0 or die "truncate: $!";
157 sub from_ssh_auth_file
($) {
160 unless (open $auth, '<', $name) {
161 warn "$name:0: error: open failed: $!\n";
164 my $tmp = new File
::Temp
;
165 while (my $line = <$auth>) {
167 next if $line =~ m/^\s*(#|$)/;
170 print $tmp "$line\n" or die "print: $!";
172 my ($length, $hash) = ssh_fprint_file
"$tmp";
173 if ($length && $hash) {
174 ssh_fprint_check
"$name:$lineno", $length, $hash;
176 warn "$name:$lineno: warning: unparsable line\n";
181 sub from_openvpn_key
($) {
184 unless (open $key, '<', $name) {
185 warn "$name:0: open failed: $!\n";
190 while (my $line = <$key>) {
192 if ($line =~ /^-----BEGIN OpenVPN Static key V1-----/) {
195 if ($line =~ /^([0-9a-f]{32})/) {
197 $line =~ s/(..)/chr(hex($1))/ge;
198 check_hash
"$name:$.", $line;
201 warn "$name:$.: warning: illegal OpenVPN file format\n";
208 sub from_ssh_host
(@
) {
211 push @lines, safe_backtick qw
/ssh-keyscan -t rsa/, @names;
212 push @lines, safe_backtick qw
/ssh-keyscan -t dsa/, @names;
214 my $tmp = new File
::Temp
;
215 for my $line (@lines) {
216 next if $line =~ /^#/;
217 my ($host, $data) = $line =~ /^(\S+) (.*)$/;
219 print $tmp "$data\n" or die "print: $!";
221 my ($length, $hash) = ssh_fprint_file
"$tmp";
222 if ($length && $hash) {
223 ssh_fprint_check
"$host", $length, $hash;
225 warn "$host: warning: unparsable line\n";
232 my ($name,$passwd,$uid,$gid,
233 $quota,$comment,$gcos,$dir,$shell,$expire) = getpwnam($user);
234 my $file = "$dir/.ssh/authorized_keys";
235 from_ssh_auth_file
$file if -r
$file;
236 $file = "$dir/.ssh/authorized_keys2";
237 from_ssh_auth_file
$file if -r
$file;
238 $file = "$dir/.ssh/known_hosts";
239 from_ssh_auth_file
$file if -r
$file;
240 $file = "$dir/.ssh/known_hosts2";
241 from_ssh_auth_file
$file if -r
$file;
242 $file = "$dir/.ssh/id_rsa.pub";
243 from_ssh_key_file
$file if -r
$file;
244 $file = "$dir/.ssh/id_dsa.pub";
245 from_ssh_key_file
$file if -r
$file;
248 sub from_user_all
() {
249 # This was one loop initially, but does not work with some Perl
253 while (my $name = getpwent) {
257 from_user
$_ for @names;
260 if (@ARGV && $ARGV[0] eq '-c') {
262 $db_file = shift @ARGV if @ARGV;
266 my $cmd = shift @ARGV;
267 if ($cmd eq 'file') {
268 for my $name (@ARGV) {
269 next if from_openvpn_key
$name;
270 from_ssh_auth_file
$name;
272 } elsif ($cmd eq 'host') {
274 } elsif ($cmd eq 'user') {
276 from_user
$_ for @ARGV;
280 } elsif ($cmd eq 'help') {
284 die "error: invalid command, use \"help\" to get help\n";