sbin/dnssec-keytool
changeset 131 d8fa60488868
parent 113 30bd047cd057
child 137 69856eb1e826
--- /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 = <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 = <KEYFILE>;
+                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 = <INDEX>;
+    close(INDEX);
+    open(INDEX, "<$zpf/.index.ksk") or die "$zpf/.index.ksk: $!\n";
+    push @keylist, <INDEX>;
+
+    # 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 = <ZONEFILE>;
+    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: