mirror of
https://github.com/zerotier/edge.git
synced 2026-05-22 16:25:05 -07:00
1527 lines
32 KiB
Perl
Executable File
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
|