Files
2018-11-13 23:20:57 +00:00

1527 lines
32 KiB
Perl
Executable File

#!/usr/bin/perl
#
# Copyright 2004-2014 SPARTA, Inc. All rights reserved. See the COPYING
# file distributed with this software for details.
#
#
# krfcheck
#
# This script checks a keyrec file for problems, potential problems,
# and inconsistencies.
#
# Problems include:
#
# - no zones defined
# - no sets defined
# - no keys defined
# - unknown zone keyrecs (key keyrec points to a non-existent
# zone keyrec)
# - mislabeled keys (key thinks it's KSK/ZSK, zone
# thinks it's a ZSK/KSK)
# - missing key from zone keyrec
# - invalid zone data values
# - existence of zone file
# - existence of KSK file
# - endtime > 1 day
# - seconds count and date string match
# - invalid set data values
# - listed sets exist
# - listed keys don't exist
# - seconds count and date string match
# - invalid key data values
# - bad encryption algorithm
# - existence of ZSK file
# - out-of-range bit values
# - non-existent random device
# - seconds count and date string match
# - expired zone keyrecs
#
# Potential problems include:
#
# - soon-to-expire zone keyrecs
# - odd zone-signing date
# - orphaned key keyrecs (not referenced by any zone keyrecs)
# - non-existent key directories
#
# Inconsistencies include:
#
# - zone fields in a key keyrec
# - key fields in a zone keyrec
#
use strict;
use Getopt::Long qw(:config no_ignore_case_always);
use Net::DNS::SEC::Tools::conf;
use Net::DNS::SEC::Tools::keyrec;
use Net::DNS::SEC::Tools::tooloptions;
#
# Version information.
#
my $NAME = "krfcheck";
my $VERS = "$NAME version: 2.1.0";
my $DTVERS = "DNSSEC-Tools Version: 2.2.3";
#######################################################################
#
# Time "constants".
#
my $DAY = (24 * 60 * 60); # Seconds in a day.
my $WEEK = (7 * $DAY); # Seconds in a week.
my $DAYSWARN = 7; # Days before an expiration warning.
#
# List of valid encryption algorithms and length ranges.
#
my @algorithms = ("rsasha1", "rsamd5", "dsa", "dh", "hmac-md5", "nsec3rsasha1");
my %length_low =
(
"rsasha1" => 512,
"rsamd5" => 512,
"dsa" => 512,
"dh" => 128,
"hmac-md5" => 1,
"nsec3rsasha1" => 512,
);
my %length_high =
(
"rsasha1" => 2048,
"rsamd5" => 2048,
"dsa" => 1024,
"dh" => 4096,
"hmac-md5" => 512,
"nsec3rsasha1" => 4096
);
#######################################################################
#
# Data required for command line options.
#
my $count = 0; # Error-count flag.
my $quiet = 0; # Quiet flag.
my $verbose = 0; # Verbose flag.
my $keychk = 0; # Keys-only flag.
my $setchk = 0; # Sets-only flag.
my $zonechk = 0; # Zones-only flag.
my %options = (); # Filled option array.
my @opts =
(
"zone", # Only check zone keyrecs.
"set", # Only check set keyrecs.
"key", # Only check key keyrecs.
"count+", # Give final error count.
"quiet+", # Don't give any output.
"verbose+", # Give lotsa output.
"Version", # Display the version number.
"help", # Give a usage message and exit.
);
my $zoneerrs = 0; # Count of errors in zones.
my $seterrs = 0; # Count of errors in sets.
my $keyerrs = 0; # Count of errors in keys.
my @krnames; # List of keyrecs in the file.
my %zones = (); # Names of zone keyrecs.
my %sets = (); # Names of set keyrecs.
my %kskkeys = (); # Names of KSK keyrecs.
my %zskkeys = (); # Names of ZSK keyrecs.
my $curtime; # Current time.
my $ret; # Return code from main().
$ret = main();
exit($ret);
#-----------------------------------------------------------------------------
# Routine: main()
#
sub main()
{
my $errors = 0; # Total error count.
#
# Parse the command line.
#
erraction(ERR_EXIT);
optsandargs();
#
# Read the keyrec file.
#
getkeyrecs($ARGV[0]);
#
# Check our watch.
#
$curtime = time();
#
# Run the zone checks.
#
zonechecks() if($zonechk);
#
# Run the set checks.
#
setchecks() if($setchk);
#
# Run the key checks.
#
keychecks() if($keychk);
#
# Calculate and report the number of errors we found.
#
$errors = $zoneerrs + $seterrs + $keyerrs;
print("$errors errors\n") if($count);
return($errors);
}
#-----------------------------------------------------------------------------
# Routine: optsandargs()
#
# Purpose: Parse our command line.
#
sub optsandargs
{
my $argc = @ARGV; # Number of command line arguments.
#
# Check our options.
#
GetOptions(\%options,@opts) || usage();
$count = $options{'count'};
$quiet = $options{'quiet'};
$verbose = $options{'verbose'};
$keychk = $options{'key'};
$setchk = $options{'set'};
$zonechk = $options{'zone'};
#
# Check a few immediate-handle options.
#
version() if(defined($options{'Version'}));
usage() if(defined($options{'help'}));
#
# Check that exclusive options really are exclusive.
#
if(($keychk && $zonechk) ||
($keychk && $setchk) ||
($setchk && $zonechk))
{
usage();
}
#
# If no record-specific options weren't given, we'll do everything.
#
if(!$zonechk && !$setchk && !$keychk)
{
$zonechk = 1;
$setchk = 1;
$keychk = 1;
}
#
# Ensure we were given a keyrec file to check.
#
usage() if($argc == 0);
}
#-----------------------------------------------------------------------------
# Routine: zonechecks()
#
# Purpose: Run some checks on the zones.
#
sub zonechecks
{
my $zkr; # Zone keyrec reference.
my %zonerec; # Zone keyrec.
my $zonename; # Zone name for looping.
my $keyname; # Key name.
my @fields = keyrec_keyfields(); # Valid key fields.
#
# Make sure some zones were defined.
#
if(length(%zones) == 0)
{
vprint("no zones defined\n");
$zoneerrs++;
return;
}
#
# Run a bunch of checks on the zones.
#
foreach my $zonename (sort(keys(%zones)))
{
vprint("checking zone $zonename\n");
$zkr = $zones{$zonename};
zonedata($zonename,$zkr);
zonesets($zonename,$zkr);
expiredzones($zonename,$zkr);
crosshkeys("zone","key",$zonename,$zkr,@fields);
}
vprint("\n");
}
#-----------------------------------------------------------------------------
# Routine: setchecks()
#
# Purpose: Run some checks on the signing sets.
#
sub setchecks
{
my $skr; # Set keyrec reference.
my %setrec; # Set keyrec.
my $setname; # Set name for looping.
my $keyname; # Key name.
my @sets = keyrec_signsets(); # Valid signing sets.
#
# Make sure the keyrec file has some set keyrecs.
#
if(length(%sets) == 0)
{
vprint("no set keyrecs defined\n");
$seterrs++;
return;
}
#
# Go through the sets and verify that each set's keys exist
# and that the set timestamp is valid.
#
foreach my $setname (sort(keys(%sets)))
{
vprint("checking set $setname\n");
setdata($setname);
}
vprint("\n");
}
#-----------------------------------------------------------------------------
# Routine: keychecks()
#
# Purpose: Run some checks on the KSKs and ZSKs.
#
sub keychecks
{
my $kr; # Keyrec reference.
my %keyrec; # Keyrec.
my @fields = keyrec_zonefields(); # Valid zone fields.
#
# Run several checks on the KSKs.
#
if(length(%kskkeys) > 0)
{
foreach my $keyname (sort(keys(%kskkeys)))
{
vprint("checking KSK key $keyname\n");
$kr = $kskkeys{$keyname};
%keyrec = %$kr;
keydata("KSK",$keyname,$kr);
keyzones("KSK",$keyname,\%keyrec);
orphankeys("KSK",$keyname);
keytypes("KSK",$keyname);
crosshkeys("KSK","zone",$keyname,$kr,@fields);
}
}
else
{
vprint("no KSK keys defined\n");
$keyerrs++;
}
vprint("\n");
#
# Run several checks on the ZSKs.
#
if(length(%zskkeys) > 0)
{
foreach my $keyname (sort(keys(%zskkeys)))
{
vprint("checking ZSK key $keyname\n");
$kr = $zskkeys{$keyname};
%keyrec = %$kr;
keydata("ZSK",$keyname,$kr);
keyzones("ZSK",$keyname,\%keyrec);
orphankeys("ZSK",$keyname);
keytypes("ZSK",$keyname);
crosshkeys("ZSK","zone",$keyname,$kr,@fields);
}
}
else
{
vprint("no ZSK keys defined\n");
$keyerrs++;
}
}
#-----------------------------------------------------------------------------
# Routine: getkeyrecs()
#
# Purpose: Load the keyrec file contents. The keyrecs are segregated
# according to type.
#
sub getkeyrecs
{
my $krfile = shift; # Keyrec file.
#
# Read the specified keyrec file.
#
keyrec_read($krfile);
@krnames = keyrec_names();
#
# Add the keyrec to one of our global type hashes.
#
foreach my $krn (sort(@krnames))
{
my $kr; # Reference to keyrec.
my $type; # Keyrec's type.
my $stype; # Keyrec's set type.
next if(!$krn);
#
# Get the keyrec, its type, and its set type.
#
$kr = keyrec_fullrec($krn);
$type = keyrec_recval($krn,'keyrec_type');
$stype = keyrec_recval($krn,'set_type');
#
# Add the keyrec to the appropriate hash.
#
if($type eq 'zone')
{
$zones{$krn} = $kr;
}
elsif(($type eq 'set') ||
(($stype eq 'kskobs') || ($stype eq 'kskrev')))
{
$sets{$krn} = $kr;
}
elsif(($type eq 'kskcur') || ($type eq 'kskpub') ||
($type eq 'kskobs') || ($type eq 'kskrev'))
{
$kskkeys{$krn} = $kr;
}
elsif(($type eq 'zskcur') || ($type eq 'zsknew') ||
($type eq 'zskpub') || ($type eq 'zskobs'))
{
$zskkeys{$krn} = $kr;
}
else
{
qprint("keyrec $krn has unknown type - \"$type\"\n");
}
}
}
##############################################################################
# #
# Zone checks #
# #
##############################################################################
#-----------------------------------------------------------------------------
# Routine: zonesets()
#
# Purpose: Verify that the signing sets of the KSKs and ZSKs exist.
#
sub zonesets
{
my $zonename = shift; # Zone name.
my $zkr = shift; # Zone keyrec reference.
my %zonerec = %$zkr; # Zone keyrec.
my $setname; # Set name.
#
# Ensure that the zone's Current KSK signing set is present and exists.
#
$setname = $zonerec{'kskcur'};
if(!defined($setname))
{
qprint("missing set: KSKCUR (zone $zonename)\n");
$zoneerrs++;
}
elsif(!defined($sets{$setname}))
{
qprint("no such key: KSKCUR set $setname (zone $zonename)\n");
$zoneerrs++;
}
#
# Ensure that if the zone's Published KSK signing set is present that
# it actually exists.
#
$setname = $zonerec{'kskpub'};
if(defined($setname))
{
if(!defined($sets{$setname}))
{
qprint("no such key: KSKPUB set $setname (zone $zonename)\n");
$zoneerrs++;
}
}
#
# Ensure that the zone's Current ZSK signing set is present and exists.
#
$setname = $zonerec{'zskcur'};
if(!defined($setname))
{
qprint("missing set: ZSKCUR set $setname (zone $zonename)\n");
$zoneerrs++;
}
elsif(!defined($sets{$setname}))
{
qprint("no such set: ZSKCUR set $setname (zone $zonename)\n");
$zoneerrs++;
}
#
# Ensure that the zone's Published ZSK signing set is present
# and exists.
#
$setname = $zonerec{'zskpub'};
if(!defined($setname))
{
qprint("missing set: ZSKPUB set $setname (zone $zonename)\n");
$zoneerrs++;
}
elsif(!defined($sets{$setname}))
{
qprint("no such set: ZSKPUB set $setname (zone $zonename)\n");
$zoneerrs++;
}
}
#-----------------------------------------------------------------------------
# Routine: expiredzones()
#
# Purpose: Check for expired zones.
#
sub expiredzones
{
my $zonename = shift; # Zone name to check.
my $zkr = shift; # Keyrec reference.
my %zonerec = %$zkr; # Zone keyrec.
my $endtime = $zonerec{'endtime'}; # Zone's endtime.
my $signsecs = $zonerec{'keyrec_signsecs'}; # Zone's sign date.
my $errflag = 0; # Error flag.
if(!defined($signsecs))
{
qprint("missing keyrec_signsecs; unable to verify expiration time for zone $zonename\n");
$errflag = 1;
$zoneerrs++;
}
if(!defined($endtime))
{
qprint("missing endtime; unable to verify expiration time for zone $zonename\n");
$errflag = 1;
$zoneerrs++;
}
return if($errflag);
$ret = checkexpire('zone',$zonename,$endtime,$signsecs);
$zoneerrs += $ret;
}
#-----------------------------------------------------------------------------
# Routine: zonedata()
#
# Purpose: Check the data in the zone keyrec.
#
sub zonedata
{
my $zonename = shift; # Zone's name.
my $kr = shift; # Keyrec reference.
my %keyrec = %$kr; # Zone table.
my $zonefile = $keyrec{'zonefile'};
my $kskdir = $keyrec{'kskdirectory'};
my $kskfile = $keyrec{'kskpath'};
my $zskdir = $keyrec{'zskdirectory'};
my $endtime = $keyrec{'endtime'};
my $signsecs = $keyrec{'keyrec_signsecs'};
my $signdate = $keyrec{'keyrec_signdate'};
my $datestr; # Converted keyrec_signsecs.
my $dateerr = 0; # Date error flag.
#
# Check the existence of the zone file.
#
if(defined($zonefile) && (! -e $zonefile))
{
qprint("missing file: zone file $zonefile (zone $zonename)\n");
$zoneerrs++;
}
#
# Check the existence of the KSK key file.
#
if(defined($kskfile) && (! -e $kskfile))
{
qprint("missing file: KSK $kskfile (zone $zonename)\n");
$zoneerrs++;
}
#
# Check the existence of the KSK key directory.
#
if(defined($kskdir) && (! -e $kskdir))
{
qprint("missing KSK directory: $kskdir (zone $zonename)\n");
$zoneerrs++;
}
#
# Check the existence of the ZSK key directory.
#
if(defined($zskdir) && (! -e $zskdir))
{
qprint("missing ZSK directory: $zskdir (zone $zonename)\n");
$zoneerrs++;
}
#
# Make sure the zone's endtime is more than a day.
#
if(defined($endtime) && ($endtime =~ /^\+[0-9]+/))
{
my $secs; # Second-count in endtime.
$endtime =~ /^\+([0-9]+)/;
$secs = $1;
if($secs < $DAY)
{
qprint("strange endtime: $secs < one day (zone $zonename)\n");
$zoneerrs++;
}
}
#
# Verify that the zone's timestamp data exists.
#
if(!defined($signsecs))
{
$dateerr = 1;
qprint("no keyrec_signsecs defined for zone $zonename\n");
$zoneerrs++;
}
if(!defined($signdate))
{
$dateerr = 1;
qprint("no keyrec_signdate defined for zone $zonename\n");
$zoneerrs++;
}
#
# Ensure that the zone's seconds count converts into the date
# stored in its date string.
#
if(!$dateerr)
{
$datestr = gmtime($signsecs);
if(defined($signdate) && ($datestr ne $signdate))
{
qprint("mismatched timestamps: $signsecs invalid for \"$signdate\" (zone $zonename)\n");
$zoneerrs++;
}
}
}
##############################################################################
# #
# Set checks #
# #
##############################################################################
#-----------------------------------------------------------------------------
# Routine: setdata()
#
# Purpose: Check the data in a set keyrec.
#
sub setdata
{
my $setname = shift; # Set to check.
my $keys; # Set's key list.
my @keys; # Set's key array.
my $dateerr = 0; # Date error flag.
my $datestr; # Date string.
my $setsecs; # Set's seconds count.
my $setdate; # Set's date string.
my $settype; # Set's type.
my $zone; # Set's owner zone.
#
# Verify the set's zone.
#
$zone = keyrec_recval($setname,'zonename');
if(!defined($zone))
{
qprint("no zone defined for set $setname\n");
$seterrs++;
}
elsif(!defined($zones{$zone}))
{
qprint("no such zone keyrec: set $setname does not have a valid zone ($zone)\n");
$seterrs++;
}
#
# Get the set's type.
#
$settype = keyrec_recval($setname,'set_type');
#
# Get the set's list of keys.
#
$keys = keyrec_recval($setname,'keys');
@keys = split / /, $keys;
if(!defined $keys)
{
qprint("no keys defined for set $setname\n");
$seterrs++;
}
#
# Make sure all of the set's keys have a key keyrec and are of the
# expected type.
#
foreach my $key (sort(@keys))
{
my $keytype; # Key's type.
my $kstype; # Key's set type.
# Get some info about this keyrec.
#
$keytype = keyrec_recval($key,'keyrec_type');
$kstype = keyrec_recval($key,'set_type');
#
# Subordinate set keyrecs may only be kskrevs and kskobses.
#
next if(($keytype eq 'set') && ($kstype eq 'kskrev'));
next if(($keytype eq 'set') && ($kstype eq 'kskobs'));
next if(defined($kstype) &&
(($keytype eq 'kskrev') || ($keytype eq 'kskobs')));
#
# Ensure the key exists as a KSK key or a ZSK key.
#
if(!defined($kskkeys{$key}) && !defined($zskkeys{$key}))
{
qprint("no such key keyrec: key $key (set $setname)\n");
# qprint("\t\t(keytype $keytype) (kstype $kstype)\n");
$seterrs++;
}
#
# Compare the key's type to the set's type.
#
if($keytype ne $settype)
{
qprint("key type (key $key, type $keytype) does not match set's type (set $setname, type $settype)\n");
$seterrs++;
}
}
#
# Get the set's timestamp data.
#
$setsecs = keyrec_recval($setname,'keyrec_setsecs');
$setdate = keyrec_recval($setname,'keyrec_setdate');
if(!defined($setsecs))
{
$dateerr = 1;
qprint("no keyrec_setsecs defined for set $setname\n");
$seterrs++;
}
if(!defined($setdate))
{
$dateerr = 1;
qprint("no keyrec_setdate defined for set $setname\n");
$seterrs++;
}
#
# Ensure that the set's seconds count converts into the date
# stored in its date string.
#
if($dateerr == 0)
{
$datestr = gmtime($setsecs);
if(defined($setdate) && ($datestr ne $setdate))
{
qprint("mismatched timestamps: $setsecs invalid for \"$setdate\" (set $setname)\n");
$seterrs++;
}
}
}
##############################################################################
# #
# Key checks #
# #
##############################################################################
#-----------------------------------------------------------------------------
# Routine: keydata()
#
# Purpose: Check the data in the key keyrec.
#
sub keydata
{
my $keytype = shift; # Keyrec's type.
my $keyname = shift; # Keyrec's name.
my $kr = shift; # Keyrec reference.
my %keyrec = %$kr; # Key table.
my $found = 0; # Algorithm-found flag.
my $length; # Key length.
my $algorithm = lc($keyrec{'algorithm'});
my $keypath = $keyrec{'keypath'};
my $random = $keyrec{'random'};
my $gensecs = $keyrec{'keyrec_gensecs'};
my $gendate = $keyrec{'keyrec_gendate'};
my $datestr; # Converted keyrec_gensecs.
my $dateerr = 0; # Date error flag.
#
# Get the key's length.
#
if($keytype eq "KSK")
{
$length = $keyrec{'ksklength'};
}
else
{
$length = $keyrec{'zsklength'};
}
#
# Search the algorithm list for this key's algorithm. If we find
# it, we'll mark that it's good.
#
if(defined($algorithm))
{
foreach my $alg (@algorithms)
{
if($alg eq $algorithm)
{
$found = 1;
last;
}
}
}
#
# If the key's encryption algorithm is bad, we'll give an error
# message. If it's okay, we'll make sure the length is in the
# range of valid lengths for the key type.
#
if(!$found)
{
qprint("invalid algorithm: \"$algorithm\" ($keytype key $keyname)\n");
$keyerrs++;
}
else
{
my $lowval = $length_low{$algorithm};
my $highval = $length_high{$algorithm};
if($lowval > $length)
{
qprint("invalid key length: length ($length) < minimum ($lowval) for $algorithm ($keytype key $keyname)\n");
$keyerrs++;
}
if($highval < $length)
{
qprint("invalid key length: length ($length) > maximum ($lowval) for $algorithm ($keytype key $keyname)\n");
$keyerrs++;
}
#
# Check the existence of the ZSK key file.
#
if(defined($keypath) && (! -e $keypath))
{
if($keyrec{'keyrec_type'} ne 'zskobs')
{
qprint("missing file: ZSK $keypath\n");
$keyerrs++;
}
}
}
#
# Check the existence of the random number file.
#
if(defined($random))
{
if(! -e $random)
{
qprint("missing file: random generator $random ($keytype key $keyname)\n");
$keyerrs++;
}
}
#
# Verify that the key's timestamp data exists.
#
if(!defined($gensecs))
{
$dateerr = 1;
qprint("no keyrec_gensecs defined for key $keyname\n");
$keyerrs++;
}
if(!defined($gendate))
{
$dateerr = 1;
qprint("no keyrec_gendate defined for key $keyname\n");
$keyerrs++;
}
#
# Ensure that the key's seconds count converts into the date
# stored in its date string.
#
$datestr = gmtime($gensecs);
if(defined($gendate) && ($datestr ne $gendate))
{
qprint("mismatched timestamps: $gensecs invalid for \"$gendate\" ($keytype key $keyname)\n");
$keyerrs++;
}
}
#-----------------------------------------------------------------------------
# Routine: keyzones()
#
# Purpose: Reverse check the key's zone.
#
sub keyzones
{
my $keytype = shift; # Keyrec's type.
my $keyname = shift; # Keyrec's name.
my $kr = shift; # Keyrec reference.
my %keyrec = %$kr; # Key table.
my $zonename; # Zone name to check.
$zonename = $keyrec{'zonename'};
if($zonename eq "")
{
qprint("no zone given: $keytype key $keyname\n");
$keyerrs++;
return;
}
if(!defined($zones{$zonename}))
{
qprint("no such zone: $zonename ($keytype key $keyname)\n");
$keyerrs++;
}
}
#-----------------------------------------------------------------------------
# Routine: orphankeys()
#
# Purpose: Look for orphaned keys -- keys which don't belong to any
# signing set.
#
sub orphankeys
{
my $keytype = shift; # Keyrec's type.
my $keyname = shift; # Keyrec's name.
my $found = 0; # Key-found flag.
#
# Go through our signing sets to see if one holds the key.
#
foreach my $set (keyrec_signsets())
{
if(keyrec_signset_haskey($set,$keyname))
{
$found = 1;
last;
}
}
#
# Go through our signing sets to see if one holds the key.
#
foreach my $set (keyrec_signsets())
{
my $kt; # Keyrec's type.
my $keys; # Keyrec's key string.
my @keys; # Keyrec's key array.
last if($found);
$kt = keyrec_recval($set,'keyrec_type');
$keys = keyrec_recval($set,'keys');
#
# Return failure if it it's a zone or set keyrec.
#
next if(($kt eq 'zone') || ($kt eq 'set'));
#
# Return failure if it's a zone or set keyrec.
#
next if($keys eq '');
#
# Put the keyrec's keys into an array of names.
#
@keys = split / /, $keys;
#
# Check the keys to see if one holds the key.
#
for(my $ind = 0;$ind < @keys; $ind++)
{
if($keys[$ind] eq $keyname)
{
$found = 1;
last;
}
}
}
#
# Register an error if no signing set holds the key.
#
if(!$found)
{
my $tstr = keyrec_recval($keyname,'keyrec_type');
qprint("orphaned key: $keytype key $keyname\t($tstr)\n");
$keyerrs++;
return;
}
}
#-----------------------------------------------------------------------------
# Routine: keytypes()
#
# Purpose: Ensure that keys have the right types.
#
sub keytypes
{
my $keytype = shift; # Keyrec's type.
my $keyname = shift; # Keyrec's name.
my $set; # Signing set.
my $ktype; # Key's actual type.
my $zonename; # Key's zone's name.
#
# Look through the zone list to ensure the key and the zone agree
# about the key's type.
#
if($keytype eq "KSK")
{
#
# Gather our needed data.
#
$zonename = $kskkeys{$keyname}{'zonename'};
$ktype = $kskkeys{$keyname}{'keyrec_type'};
$set = $zones{$zonename}{$ktype};
}
else
{
#
# Gather our needed data.
#
$zonename = $zskkeys{$keyname}{'zonename'};
$ktype = $zskkeys{$keyname}{'keyrec_type'};
$set = $zones{$zonename}{$ktype};
}
#
# Obsolete and revoked keys aren't listed in the zone keyrec.
#
return if(($ktype eq "zskobs") ||
($ktype eq "kskobs") ||
($ktype eq "kskrev"));
#
# Register an error if the key isn't in the appropriate signing set.
#
if(!keyrec_signset_haskey($set,$keyname))
{
qprint("mislabeled key type: $keytype key $keyname not in set $set\n");
$keyerrs++;
}
}
##############################################################################
# #
# Utilities #
# #
##############################################################################
#-----------------------------------------------------------------------------
# Routine: checkexpire()
#
sub checkexpire
{
my $type = shift; # Zone name for looping.
my $name = shift; # Zone name for looping.
my $endtime = shift; # Zone's endtime.
my $signsecs = shift; # Zone's sign date.
my $errs = 0; # Error count.
my $secs; # Seconds in "+nnn" endtime.
my $days; # Days until zone expiration.
my $daystr = "days"; # Days string.
my $finaltime; # Time zone expires.
#
# Ensure that the keyrec's sign date is older than the current time.
#
if($signsecs > $curtime)
{
qprint("bizarre time: sign date more recent than current date ($type $name)\n");
$errs++;
}
#
# Get the number of seconds until the keyrec's end time.
#
if($endtime =~ /^+/)
{
$endtime =~ /\+([0-9]+)/;
$secs = $1;
}
#
# Calculate the keyrec's expiration date.
#
$finaltime = $signsecs + $secs;
#
# Make sure the keyrec hasn't expired yet. If it hasn't, we'll
# check to see if it's close to expiration.
#
if($finaltime <= $curtime)
{
$secs = $curtime - $finaltime;
$days = int($secs / $DAY);
$daystr = "day" if ($days == 1);
qprint("signtime: $type expired $days $daystr ago ($type $name)\n");
$errs++;
}
else
{
#
# Figure out how many days are left until expiration.
#
$secs = $finaltime - $curtime;
$days = int($secs / $DAY);
$daystr = "day" if ($days == 1);
#
# Check if the keyrec will soon expire.
#
if($days <= $DAYSWARN)
{
qprint("signtime: $type expires in $days $daystr ($type $name)\n");
$errs++;
}
}
return($errs);
}
#-----------------------------------------------------------------------------
# Routine: crosshkeys()
#
# Purpose: Ensure that all the keyrec's fields are valid for that
# type of keyrec.
#
sub crosshkeys
{
my $krtype = shift; # Type of keyrec.
my $fldtype = shift; # Type of @fields' keys.
my $krname = shift; # Keyrec's name.
my $kr = shift; # Reference to keyrec.
my @fields = shift; # Hash keys to check for.
my %keyrec = %$kr; # Caller's keyrec.
my $errs = 0; # Error count.
#
# None of the entries in @fields should be hash keys in the
# keyrec. We'll check to ensure all's well.
#
foreach my $key (@fields)
{
#
# Skip internal-use fields.
#
if($key eq "keyrec_type")
{
next;
}
if(exists($keyrec{$key}))
{
qprint("bad entry: $fldtype hash-key $key in a $krtype record ($krtype $krname)\n");
$errs++;
}
}
return($errs);
}
#-----------------------------------------------------------------------------
# Routine: dumpdata()
#
# Purpose: Display a bunch of data. This is only for debugging.
#
sub dumpdata
{
my $keyname; # Key name for looping.
my $zonename; # Zone name for looping.
return if(!$verbose);
print "zones:\n";
foreach $zonename (sort(keys(%zones)))
{
print "\t$zonename\n";
}
print "\n";
print "ksks:\n";
foreach $keyname (sort(keys(%kskkeys)))
{
print "\t$keyname\n";
}
print "\n";
print "zsks:\n";
foreach $keyname (sort(keys(%zskkeys)))
{
print "\t$keyname\n";
}
print "\n";
}
#-----------------------------------------------------------------------------
# Routine: qprint()
#
# Purpose: Only print if -quiet wasn't given.
#
sub qprint
{
my $line = shift;
if(!$quiet)
{
print $line;
}
}
#-----------------------------------------------------------------------------
# Routine: vprint()
#
# Purpose: Only print if -verbose was given.
#
sub vprint
{
my $line = shift;
if($verbose)
{
qprint($line);
}
}
#----------------------------------------------------------------------
# Routine: version()
#
# Purpose: Print the version number(s) and exit.
#
sub version
{
print STDERR "$VERS\n";
print STDERR "$DTVERS\n";
exit(0);
}
#-----------------------------------------------------------------------------
# Routine: usage()
#
sub usage
{
print STDERR "usage: krfcheck [options] <keyrec-file>\n";
print STDERR " options:\n";
print STDERR " -zone only check zone keyrecs\n";
print STDERR " -set only check set keyrecs\n";
print STDERR " -key only check key keyrecs\n";
print STDERR " -count display an error count\n";
print STDERR " -quiet give no output\n";
print STDERR " -verbose give verbose output\n";
print STDERR " -Version print version numbers\n";
print STDERR " -help print this message\n";
exit(0);
}
1;
##############################################################################
#
=pod
=head1 NAME
krfcheck - Check a DNSSEC-Tools I<keyrec> file for problems and
inconsistencies
=head1 SYNOPSIS
krfcheck [-zone | -set | -key] [-count] [-quiet]
[-verbose] [-Version] [-help] keyrec-file
=head1 DESCRIPTION
This script checks a I<keyrec> file for problems, potential problems,
and inconsistencies.
Recognized problems include:
=over 4
=item * no zones defined
The I<keyrec> file does not contain any zone I<keyrec>s.
=item * no sets defined
The I<keyrec> file does not contain any set I<keyrec>s.
=item * no keys defined
The I<keyrec> file does not contain any key I<keyrec>s.
=item * unknown zone I<keyrec>s
A set I<keyrec> or a key I<keyrec> references a non-existent zone I<keyrec>.
=item * missing key from zone I<keyrec>
A zone I<keyrec> does not have both a KSK key and a ZSK key.
=item * missing key from set I<keyrec>
A key listed in a set I<keyrec> does not have a key I<keyrec>.
=item * expired zone I<keyrec>s
A zone has expired.
=item * mislabeled key
A key is labeled as a KSK (or ZSK) and its owner zone has it labeled as the
opposite.
=item * invalid zone data values
A zone's I<keyrec> data are checked to ensure that they are valid. The
following conditions are checked: existence of the zone file, existence of
the KSK file, existence of the KSK and ZSK directories, the end-time is
greater than one day, and the seconds-count and date string match.
=item * invalid key data values
A key's I<keyrec> data are checked to ensure that they are valid. The
following conditions are checked: valid encryption algorithm, key length
falls within algorithm's size range, random generator file exists, and the
seconds-count and date string match.
=back
Recognized potential problems include:
=over 4
=item * imminent zone expiration
A zone will expire within one week.
=item * odd zone-signing date
A zone's recorded signing date is later than the current system clock.
=item * orphaned keys
A key I<keyrec> is unreferenced by any set I<keyrec>.
=item * missing key directories
A zone I<keyrec>'s key directories (I<kskdirectory> or I<zskdirectory>) does
not exist.
=back
Recognized inconsistencies include:
=over 4
=item * key-specific fields in a zone I<keyrec>
A zone I<keyrec> contains key-specific entries. To allow for site-specific
extensibility, B<krfcheck> does not check for undefined I<keyrec> fields.
=item * zone-specific fields in a key I<keyrec>
A key I<keyrec> contains zone-specific entries. To allow for site-specific
extensibility, B<krfcheck> does not check for undefined I<keyrec> fields.
=item * mismatched zone timestamp
A zone's seconds-count timestamp does not match its textual timestamp.
=item * mismatched set timestamp
A set's seconds-count timestamp does not match its textual timestamp.
=item * mismatched key timestamp
A key's seconds-count timestamp does not match its textual timestamp.
=back
=head1 OPTIONS
=over 4
=item B<-zone>
Only perform checks of zone I<keyrec>s. This option may not be combined with
the B<-set> or B<-key> options.
=item B<-set>
Only perform checks of set I<keyrec>s. This option may not be combined with
the B<-zone> or B<-key> options.
=item B<-key>
Only perform checks of key I<keyrec>s. This option may not be combined with
the B<-set> or B<-zone> options.
=item B<-count>
Display a final count of errors.
=item B<-quiet>
Do not display messages. This option supersedes the setting of the B<-verbose>
option.
=item B<-verbose>
Display many messages. This option is subordinate to the B<-quiet> option.
=item B<-Version>
Displays the version information for B<krfcheck> and the DNSSEC-Tools package.
=item B<-help>
Display a usage message.
=back
=head1 COPYRIGHT
Copyright 2004-2014 SPARTA, Inc. All rights reserved.
See the COPYING file included with the DNSSEC-Tools package for details.
=head1 AUTHOR
Wayne Morrison, tewok@tislabs.com
=head1 SEE ALSO
B<cleankrf(8)>,
B<fixkrf(8)>,
B<lskrf(1)>,
B<zonesigner(8)>
B<Net::DNS::SEC::Tools::keyrec.pm(3)>
B<file-keyrec(5)>
=cut