def check_kasp_file(kasp_file)
begin
File.open((kasp_file.to_s+"").untaint, 'r') {|file|
begin
doc = REXML::Document.new(file)
rescue Exception => e
log(LOG_CRIT, "Can't understand #{file} - exiting")
exit(1)
end
policy_names = []
doc.elements.each('KASP/Policy') {|policy|
name = policy.attributes['name']
if (policy_names.include?name)
log(LOG_ERR, "Two policies exist with the same name (#{name})")
end
policy_names.push(name)
resign_secs = get_duration(policy,'Signatures/Resign', kasp_file)
refresh_secs = get_duration(policy, 'Signatures/Refresh', kasp_file)
if (refresh_secs != 0 && refresh_secs <= resign_secs)
log(LOG_ERR, "The Refresh interval (#{refresh_secs} seconds) for " +
"#{name} Policy in #{kasp_file} is less than or equal to the Resign interval" +
" (#{resign_secs} seconds)")
end
default_secs = get_duration(policy, 'Signatures/Validity/Default', kasp_file)
denial_secs = get_duration(policy, 'Signatures/Validity/Denial', kasp_file)
if (default_secs <= refresh_secs)
log(LOG_ERR, "Validity/Default (#{default_secs} seconds) for #{name} " +
"policy in #{kasp_file} is less than the Refresh interval " +
"(#{refresh_secs} seconds)")
end
if (denial_secs <= refresh_secs)
log(LOG_ERR, "Validity/Denial (#{denial_secs} seconds) for #{name} " +
"policy in #{kasp_file} is less than or equal to the Refresh interval " +
"(#{refresh_secs} seconds)")
end
jitter_secs = get_duration(policy, 'Signatures/Jitter', kasp_file)
max_default_denial=[default_secs, denial_secs].max
max_default_denial_type = max_default_denial == default_secs ? "Default" : "Denial"
if (jitter_secs > (max_default_denial * 0.5))
log(LOG_WARNING, "Jitter time (#{jitter_secs} seconds) is large" +
" compared to Validity/#{max_default_denial_type} " +
"(#{max_default_denial} seconds) for #{name} policy in #{kasp_file}")
end
if (jitter_secs > default_secs)
log(LOG_ERR, "Jitter time (#{jitter_secs}) is greater than the Default Validity (#{default_secs}) for #{name} policy in #{kasp_file}")
end
if (jitter_secs > denial_secs)
log(LOG_ERR, "Jitter time (#{jitter_secs}) is greater than the Denial Validity (#{denial_secs}) for #{name} policy in #{kasp_file}")
end
inception_offset_secs = get_duration(policy, 'Signatures/InceptionOffset', kasp_file)
if (inception_offset_secs > (60 * 60))
log(LOG_WARNING, "InceptionOffset is higher than expected " +
"(#{inception_offset_secs} seconds) for #{name} policy in #{kasp_file}")
end
publish_safety_secs = get_duration(policy, 'Keys/PublishSafety', kasp_file)
retire_safety_secs = get_duration(policy, 'Keys/RetireSafety', kasp_file)
ttl_secs = get_duration(policy, 'Keys/TTL', kasp_file)
[{publish_safety_secs => "Keys/PublishSafety"}, {retire_safety_secs => "Keys/RetireSafety"}].each {|pair|
pair.each {|time, label|
if (time < (0.1 * ttl_secs))
log(LOG_WARNING, "#{label} (#{time} seconds) in #{name} policy" +
" in #{kasp_file} is less than 0.1 * TTL (#{ttl_secs} seconds)")
end
if (time > (5 * ttl_secs))
log(LOG_WARNING, "#{label} (#{time} seconds) in #{name} policy" +
" in #{kasp_file} is more than 5 * TTL (#{ttl_secs} seconds)")
end
}
}
denial_type = nil
if (policy.elements['Denial/NSEC'])
denial_type = "NSEC"
else
denial_type = "NSEC3"
policy.each_element('Denial/NSEC3/Hash/') {|hash|
alg = hash.elements["Algorithm"].text
if (alg.to_i != 1)
log(LOG_ERR, "NSEC3 Hash algorithm is #{alg} but should be 1");
end
}
end
max = 9999999999999999
ksk_lifetime = max
zsk_lifetime = max
policy.each_element('Keys/ZSK') {|zsk|
check_key(zsk, "ZSK", name, kasp_file, denial_type)
zskl = get_duration(zsk, 'Lifetime', kasp_file)
zsk_lifetime = [zsk_lifetime, zskl].min
}
policy.each_element('Keys/KSK') {|ksk|
check_key(ksk, "KSK", name, kasp_file, denial_type)
kskl = get_duration(ksk, 'Lifetime', kasp_file)
ksk_lifetime = [ksk_lifetime, kskl].min
}
if ((ksk_lifetime != max) && (zsk_lifetime != max) && (ksk_lifetime < zsk_lifetime))
log(LOG_WARNING, "KSK minimum lifetime (#{ksk_lifetime} seconds)" +
" is less than ZSK minimum lifetime (#{zsk_lifetime} seconds)"+
" for #{name} Policy in #{kasp_file}")
end
if (denial_type == "NSEC3")
resign_secs = get_duration(policy,'Signatures/Resign', kasp_file)
resalt_secs = get_duration(policy,'Denial/NSEC3/Resalt', kasp_file)
if (resalt_secs)
if (resalt_secs < resign_secs)
log(LOG_WARNING, "NSEC3 resalt interval (#{resalt_secs}) is less than" +
" signature resign interval (#{resign_secs})" +
" for #{name} Policy in #{kasp_file}")
end
end
end
resigns_per_day = (60 * 60 * 24) / resign_secs
if (resigns_per_day > 99)
policy.each_element('Zone/SOA/Serial') {|serial|
if (serial.text.downcase == "datecounter")
log(LOG_ERR, "In #{kasp_file}, policy #{name}, serial type datecounter used"+
" but #{resigns_per_day} re-signs requested."+
" No more than 99 re-signs per day should be used with datecounter"+
" as only 2 digits are allocated for the version number")
elsif !(["unixtime", "datecounter", "keep", "counter"].include?serial.text.downcase)
log(LOG_ERR, "In #{kasp_file}, policy #{name}, unknown Serial type encountered ('#{serial.text}')." +
" Should be either 'unixtime', 'counter', 'datecounter' or 'keep'")
end
}
end
["Signatures/Resign", "Signatures/Refresh", "Signatures/Validity/Default",
"Signatures/Validity/Denial", "Signatures/Jitter",
"Signatures/InceptionOffset", "Keys/RetireSafety", "Keys/PublishSafety",
"Keys/Purge", "NSEC3/Resalt", "SOA/Minimum", "ZSK/Lifetime",
"KSK/Lifetime", "TTL", "PropagationDelay"].each {|element|
policy.each_element(element) {|el| check_duration_element_proc(el, name, element, kasp_file)}
}
}
if (!policy_names.include?"default")
log(LOG_WARNING, "No policy named 'default' in #{kasp_file}. This " +
"means you will need to refer explicitly to the policy for each zone")
end
}
rescue Errno::ENOENT
log(LOG_ERR, "Can't find KASP config file : #{kasp_file}")
end
end