diff -r 5578cb7933c1 -r d8fa60488868 sbin/dnssec-keytool --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sbin/dnssec-keytool Mon Jun 06 10:10:44 2011 +0200 @@ -0,0 +1,349 @@ +#! /usr/bin/perl + +use v5.10; +use warnings; +use strict; +use File::Temp; +use Getopt::Long; +use Pod::Usage; +use File::Basename; +use Net::LibIDN qw(:all); +use if $ENV{DEBUG} => "Smart::Comments"; +use DNStools::Config qw(get_config); + +my $ME = basename $0; + +sub rm_keys($@); +sub check_zone($@); +sub create_ksk($@); +sub create_zsk(@); +sub post_create($@); + +my $CHARSET = "UTF-8"; +my %cf; + +MAIN: { + + %cf = get_config(); + my $cmd; + + system("command -v dnssec-keygen &>/dev/null"); + die "$ME: command 'dnssec-keygen' not found in $ENV{PATH}\n" if $?; + + GetOptions( + "zsk" => sub { push @$cmd => "zsk" }, + "ksk" => sub { push @$cmd => "ksk" }, + "rm" => sub { push @$cmd => "rm" }, + "check" => sub { $cmd = "check" }, + "h|help" => sub { pod2usage(-exit => 0, -verbose => 1) }, + "m|man" => sub { + pod2usage( + -exit => 0, + # "system('perldoc -V &>/dev/null')" appears shorter, but may not + # do what you expect ( it still returns 0 on debian squeeze with + # dash as system shell even if cannot find the command in $PATH) + -noperldoc => system('perldoc -V >/dev/null 2>&1'), + -verbose => 2 + ); + }, + ) + and @ARGV + and @$cmd == 1 + and $cmd = $cmd->[0] + or pod2usage; + + # checks the zones in argv if they're managed ones + my @zones; + foreach my $utf8zone (@ARGV) { + my $zone = idn_to_ascii($utf8zone, $CHARSET); + + die "zone $zone is not managed\n" + if not -f "$cf{master_dir}/$zone/$zone"; + + push @zones, $zone; + } + + given ($cmd) { + when ("zsk") { exit create_zsk(@zones) }; + #when ("ksk") { return create_ksk(@zones) }; + #when ("check") { return check_zone(@zones) }; + #when ("rm") { return rm_keys(@zones) }; + default { die "not implemented\n" }; + }; +} + +sub rm_keys ($@) { + + # deletes all the keys were handed over -rm in argv + my ($master_dir, @zone) = @_; + + for (@zone) { + my $zone = $_; + + my $zpf = "$master_dir/$zone"; + my $ep = 0; + + if (-e "$zpf/$zone.signed") { + unlink "$zpf/$zone.signed" and $ep = 1; + } + if (-e "$zpf/.keycounter") { + unlink "$zpf/.keycounter" and $ep = 1; + } + if (-e "$zpf/.index.ksk") { + unlink "$zpf/.index.ksk" and $ep = 1; + } + if (-e "$zpf/.index.zsk") { + unlink "$zpf/.index.zsk" and $ep = 1; + } + if (-e "$zpf/dsset-$zone.") { + unlink "$zpf/dsset-$zone." and $ep = 1; + } + if (-e "$zpf/keyset-$zone.") { + unlink "$zpf/keyset-$zone." and $ep = 1; + } + + for (glob("$zpf/K$zone*")) { + chomp($_); + unlink("$_"); + } + + if ($ep == 1) { + print " * $zone: removed key-set\n"; + } + + open(my $old, "$zpf/$zone") or die "$zpf/$zone: $!\n"; + my $fh = File::Temp->new(DIR => $zpf) + or die "Can't create tmpfile: $!\n"; + print $fh grep { not /^\s*\$INCLUDE.*"K$zone.*\.key"/i } <$old>; + rename($fh->filename => "$zpf/$zone") + or die "Can't rename " . $fh->filename . " to $zpf/$zone: $!\n"; + } +} + +sub create_ksk ($@) { + my ($master_dir, @zone) = @_; + my @index; + my $keyname; + + for (@zone) { + my $zone = $_; + my $zpf = "$master_dir/$zone"; + + $keyname = + `cd $zpf && dnssec-keygen -a RSASHA1 -b 2048 -f KSK -n ZONE $zone`; + + unless (-f "$zpf/.index.ksk") { @index = (); } + else { + open(INDEX, "$zpf/.index.ksk") or die "$zpf/.index.ksk: $!\n"; + @index = ; + close(INDEX); + } + + push @index, $keyname; + if (@index > 2) { shift(@index); } + + { + my $fh = File::Temp->new(DIR => "$zpf") + or die "Can't create tmpdir: $!\n"; + print $fh join "" => @index, ""; + rename($fh->filename => "$zpf/.index.ksk") + or die "Can't rename " + . $fh->filename + . " to $zpf/.index.ksk: $!\n"; + } + + chomp($keyname); + print " * $zone: new KSK $keyname\n"; + print "!! THE KSK must be published !! \n"; + + } +} + +sub create_zsk (@) { + my @zones = @_; + + my $keyname; + + foreach my $zone (@zones) { + my $dir = "$cf{master_dir}/$zone"; + + chomp($keyname = `cd $dir && dnssec-keygen -a RSASHA1 -b 512 -n ZONE $zone`); + + my @index; + open(my $idx, "+>>", "$dir/.index.zsk") or die "Can't open $dir/.index.zsk: $!\n"; + seek($idx, 0, 0); + chomp(@index = <$idx>); + + push @index, $keyname; + shift @index if @index > 2; + + truncate($idx, 0); + print $idx join "\n" => @index, ""; + close($idx); + + say "$zone: new ZSK $keyname"; + + open(my $kc, ">", "$dir/.keycounter") or die "$dir/.keycounter: $!\n"; + print $kc "0\n"; + close($kc); + } +} + +sub check_zone ($@) { + my ($master_dir, @zone) = @_; + + for (@zone) { + my $zone = $_; + my $zpf = "$master_dir/$zone"; + my $keyfile; + my @content; + my @keylist; + + for (<$zpf/*>) { + if (m#(K$zone.*\.key)#) { + $keyfile = $1; + open(KEYFILE, "<", "$zpf/$keyfile") + or die "$zpf/$keyfile: $!\n"; + @content = ; + close(KEYFILE); + for (@content) { + if (m#DNSKEY.257#) { + push @keylist, $keyfile; + } + } + } + } + + open(INDEX, ">$zpf/.index.ksk") or die "$zpf/.index.ksk: $!\n"; + for (@keylist) { + s#\.key##; + print INDEX "$_\n"; + } + close(INDEX); + + print " * $zone: new .index.ksk created\n"; + if (-f "$zpf/.index.zsk") { + unlink("$zpf/.index.zsk") or die "$zpf/.index.zsk: $!\n"; + } + } +} + +sub post_create ($@) { + my ($master_dir, @zone) = @_; + for (@zone) { + my $zone = $_; + `touch $master_dir/$zone/$zone`; + &kill_useless_keys($zone, $master_dir); + &key_to_zonefile($zone, $master_dir); + } +} + +sub kill_useless_keys ($@) { + + # the function deletes all keys that are not available in the zone + + my $zone = $_[0]; + my $master_dir = $_[1]; + my @keylist = (); + my $zpf = "$master_dir/$zone"; + + open(INDEX, "<$zpf/.index.zsk") or die "$zpf/.index.zsk: $!\n"; + @keylist = ; + close(INDEX); + open(INDEX, "<$zpf/.index.ksk") or die "$zpf/.index.ksk: $!\n"; + push @keylist, ; + + # shortened the key name from the index file on the id in order to + # be able to compare + for (@keylist) { + chomp; + s#K.*\+.*\+(.*)#$1#; + } + + # reviewed every key file (KSK, ZSK), whether they are described in + # the respective index file. if not they will be deleted. + for (glob("$master_dir/$zone/K*")) { + chomp; + my $file = $_; + my $rm_count = 1; + my $keyname; + for (@keylist) { + if ($file =~ /$_/) { $rm_count = 0; } + } + if ($rm_count == 1) { + unlink "$file"; + if ($file =~ /$zpf\/(.*\.key)/) { + print " * $zone: Key $1 removed \n"; + } + } + } +} + +sub key_to_zonefile ($@) { + + # the function added all keys to the indexfile + my $zone = $_[0]; + my $master_dir = $_[1]; + my $zpf = "$master_dir/$zone"; + my @old_content; + my @new_content = (); + + open(ZONEFILE, "<$zpf/$zone"); + @old_content = ; + close(ZONEFILE); + + for (@old_content) { + unless (m#INCLUDE.*key#) { push @new_content, $_; } + } + + for (<$zpf/*>) { + if (m#(.*\/)(K.*\.key)#) { + push @new_content, "\$INCLUDE \"$2\"\n"; + } + } + open(ZONEFILE, ">$zpf/$zone") or die "$zpf/$zone: $!\n"; + print ZONEFILE @new_content; + close(ZONEFILE); +} + +__END__ + +=pod + +=head1 NAME + + dnssec-keytool - key management + +=head1 SYNOPSIS + + dnssec-keytool {--zsk|--ksk|--rm|--check} zone... + +=head1 DESCRIPTION + +Blabla. + +=head1 OPTIONS + +=over + +=item B<--zsk> + +Create a new ZSK for the zones. + +=item B<--ksk> + +Create a new KSK for the zones. + +=item B<--rm> + +Remote all key material from the zones. + +=item B<--check> + +??? + +=back + +=cut + +# vim:sts=4 sw=4 aw ai sm: