We keep running into a persistent problem with our OpenDNSSEC setup where the signer does not seem to respect the SOA serial in the input zone when we use datecounter mode. The bug is very subtle since it only bites us when we first sign a zone and switch our master name server from the database source to the output of the signer; then a mismatch in SOA serials stops a transfer of the updated zone from occurring.
I investigated the code in the signer that takes care of handling the SOA and I believe I have found the cause of this problem. Here is the relevant code snippet:
[from function zonedata_update_serial in zonedata.c@1226]
if (strncmp(sc->soa_serial, "datecounter", 11) == 0) {
soa = (uint32_t) time_datestamp(0, "%Y%m%d", NULL) * 100;
if (!zd->initialized) {
if (!DNS_SERIAL_GT(soa, zd->inbound_serial))
} else if (!DNS_SERIAL_GT(soa, prev))
{ soa = prev + 1; }}
What this code seems to do in "words" is:
Compute SOA from current time of day; this computation results in the "first SOA for today", so that would be 2013020100 for February 1st 2013
Check if zone data is initialised; I have checked, when a zone is first read this evaluates as false so the branch is entered
[in !zd->initialized branch]
Check if SOA serial from input zone is greater than inbound serial
[in !DNS_SERIAL_GT(soa, zd->inbound_serial)]
Warn user that inbound serial is greater than expected
Take inbound serial, add 1 and take that as current SOA serial
else
Do nothing, keep computed SOA serial which is now guaranteed to be
higher than the input SOA serial, so no problem
else
[in zd->initialized branch] <-- this happens when a zone is known to the signer
Check if computed SOA is larger than current internal state
[SOA not larger than internal state]
Take computed SOA serial and add 1
else
Keep computed SOA serial
The bug is in the last bit (so the branch where zone data is already initialised). In this branch, the signer does not check whether the current input zone SOA serial is larger than or equal to its internal state. Therefore, if the input zone SOA serial changes the signer ignores this and happily keeps on using its internal state.
The patch is very simple, the signer should also check if the current input zone serial is larger than its internal state and use that and add 1 of that is the case.
The code seems to be built on the assumption that if the inbound SOA serial is higher than the internal state, that that is an invalid "datecounter" serial number (at least, that is what I derive from the warning message printed when the internal state is not yet initialised and the SOA serial in the input zone is greater than what the signer has computed it to be). In my opinion that is an incorrect assumption; the signer does not do a syntax check on the data to see if it is really formatted as a datecounter SOA serial so this assumption does not hold. For example: say we turn on DNSSEC signing for a zone that has been modified today and has current SOA serial YYYYMMDD05; the signer would compute as starting serial "YYYYMMDD00" and output the warning message (that datecounter cannot be used). This assertion is incorrect, because YYYYMMDD05 is a perfectly fine datecounter SOA serial, it's just not the first one for that given day. The signer than does what it should do and increments this serial by 1 and uses that anyway. So the warning message should also be removed in my opinion. As long as the signer does not do input validation to check if the inbound SOA serial actually conforms to the datecounter format it should not make assumptions as to the validity of the input data (and to be clear, I don't think it should do input validation, if the user selects "datecounter" it is perfectly valid to assume that they know what they are doing ).
As it turns out, this is exactly the behaviour of the Python signer in OpenDNSSEC 1.0 and 1.1. I've included a patch as attachment to this issue.