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.
15 usage: $0 [OPTIONS...] COMMAND [ARGUMENTS...]
19 file: examine files on the command line for weak keys
20 host: examine the specified hosts for weak SSH keys
21 user: examine user SSH keys for weakness; examine all users if no
23 help: show this help screen
27 -c FILE: set the database cache file name (default: dowkd.db)
29 dowkd currently handles OpenSSH host and user keys and OpenVPN shared
30 secrets, as long as they use default key lengths and have been created
31 on a little-endian architecture (such as i386 or amd64). Note that
32 the blacklist by dowkd may be incomplete; it is only intended as a
45 my $db_file = 'dowkd.db';
51 $db = tie
%db, 'DB_File', $db_file, O_RDWR
| O_CREAT
, 0777, $DB_BTREE
52 or die "error: could not open database: $!\n";
54 $db{''} = $db_version;
55 while (my $line = <DATA
>) {
56 next if $line =~ /^\**$/;
58 $line =~ /^[0-9a-f]{32}$/ or die "error: invalid data line";
59 $line =~ s/(..)/chr(hex($1))/ge;
68 $db = tie
%db, 'DB_File', $db_file, O_RDONLY
, 0777, $DB_BTREE
69 or die "error: could not open database: $!\n";
70 my $stored_version = $db{''};
71 $stored_version && $stored_version eq $db_version or create_db
;
78 sub safe_backtick
(@
) {
82 or die "error: failed to spawn $args[0]: $!\n";
88 @result = scalar(<$fh>);
91 $?
== 0 or return undef;
100 my ($name, $hash) = @_;
101 if (exists $db{$hash}) {
102 print "$name: weak key\n";
106 sub ssh_fprint_file
($) {
108 my $data = safe_backtick qw
/ssh-keygen -l -f/, $name;
109 defined $data or return ();
110 my @data = $data =~ /^(\d+) ([0-9a-f]{2}(?::[0-9a-f]{2}){15})/;
111 return @data if @data == 2;
115 sub ssh_fprint_check
($$$) {
116 my ($name, $length, $hash) = @_;
117 if ($length == 1024 || $length == 2048) {
119 $hash =~ s/(..)/chr(hex($1))/ge;
120 check_hash
$name, $hash;
122 warn "$name: warning: no suitable blacklist\n";
126 sub from_ssh_key_file
($) {
128 my ($length, $hash) = ssh_fprint_file
$name;
129 if ($length && $hash) {
130 ssh_fprint_check
"$name:1", $length, $hash;
132 warn "$name:1: warning: failed to parse SSH key file\n";
138 seek $tmp, 0, 0 or die "seek: $!";
139 truncate $tmp, 0 or die "truncate: $!";
142 sub from_ssh_auth_file
($) {
145 unless (open $auth, '<', $name) {
146 warn "$name:0: error: open failed: $!\n";
149 my $tmp = new File
::Temp
;
150 while (my $line = <$auth>) {
154 print $tmp "$line\n" or die "print: $!";
156 my ($length, $hash) = ssh_fprint_file
"$tmp";
157 if ($length && $hash) {
158 ssh_fprint_check
"$name:$lineno", $length, $hash;
160 warn "$name:$lineno: warning: unparsable line\n";
165 sub from_openvpn_key
($) {
168 unless (open $key, '<', $name) {
169 warn "$name:0: open failed: $!\n";
174 while (my $line = <$key>) {
176 if ($line =~ /^-----BEGIN OpenVPN Static key V1-----/) {
179 if ($line =~ /^([0-9a-f]{32})/) {
181 $line =~ s/(..)/chr(hex($1))/ge;
182 check_hash
"$name:$.", $line;
185 warn "$name:$.: warning: illegal OpenVPN file format\n";
192 sub from_ssh_host
(@
) {
195 push @lines, safe_backtick qw
/ssh-keyscan -t rsa/, @names;
196 push @lines, safe_backtick qw
/ssh-keyscan -t dsa/, @names;
198 my $tmp = new File
::Temp
;
199 for my $line (@lines) {
200 next if $line =~ /^#/;
201 my ($host, $data) = $line =~ /^(\S+) (.*)$/;
203 print $tmp "$data\n" or die "print: $!";
205 my ($length, $hash) = ssh_fprint_file
"$tmp";
206 if ($length && $hash) {
207 ssh_fprint_check
"$host", $length, $hash;
209 warn "$host: warning: unparsable line\n";
216 my ($name,$passwd,$uid,$gid,
217 $quota,$comment,$gcos,$dir,$shell,$expire) = getpwnam($user);
218 my $file = "$dir/.ssh/authorized_keys";
219 from_ssh_auth_file
$file if -r
$file;
220 $file = "$dir/.ssh/id_rsa.pub";
221 from_ssh_key_file
$file if -r
$file;
222 $file = "$dir/.ssh/id_dsa.pub";
223 from_ssh_key_file
$file if -r
$file;
226 sub from_user_all
() {
228 while (my $name = getpwent) {
234 if (@ARGV && $ARGV[0] eq '-c') {
236 $db_file = shift @ARGV if @ARGV;
240 my $cmd = shift @ARGV;
241 if ($cmd eq 'file') {
242 for my $name (@ARGV) {
243 next if from_openvpn_key
$name;
244 from_ssh_auth_file
$name;
246 } elsif ($cmd eq 'host') {
248 } elsif ($cmd eq 'user') {
250 from_user
$_ for @ARGV;
254 } elsif ($cmd eq 'help') {
257 die "error: invalid command, use \"help\" to get help\n";