PrimaryZone: Fixed minor issue with zone signing. Added missing notify trigger calls when converting between NSEC and NSEC3. Updated GetDSPublishedPrivateKeys() to use DirectQueryAsync() to get DS records. Updated implementation to validate if the RRSet type supports signing. Code refactoring done.

This commit is contained in:
Shreyas Zare
2022-03-06 16:05:42 +05:30
parent d813fe2fe1
commit af88ea9e5d

View File

@@ -626,9 +626,6 @@ namespace DnsServerCore.Dns.Zones
if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
throw new DnsServerException("Cannot sign zone: the zone is already signed.");
if (_dnssecPrivateKeys is not null)
throw new DnsServerException("Cannot sign zone: the zone is already signed.");
if (iterations > 50)
throw new ArgumentOutOfRangeException(nameof(iterations), "NSEC3 iterations valid range is 0-50");
@@ -687,9 +684,6 @@ namespace DnsServerCore.Dns.Zones
if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
throw new DnsServerException("Cannot sign zone: the zone is already signed.");
if (_dnssecPrivateKeys is not null)
throw new DnsServerException("Cannot sign zone: the zone is already signed.");
if (iterations > 50)
throw new ArgumentOutOfRangeException(nameof(iterations), "NSEC3 iterations valid range is 0-50");
@@ -727,80 +721,91 @@ namespace DnsServerCore.Dns.Zones
private void SignZone(bool useNSec3, ushort iterations, byte saltLength, uint dnsKeyTtl)
{
//update private key state
foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
try
{
DnssecPrivateKey privateKey = privateKeyEntry.Value;
switch (privateKey.KeyType)
//update private key state
foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
{
case DnssecPrivateKeyType.KeySigningKey:
privateKey.SetState(DnssecPrivateKeyState.Ready);
break;
DnssecPrivateKey privateKey = privateKeyEntry.Value;
case DnssecPrivateKeyType.ZoneSigningKey:
privateKey.SetState(DnssecPrivateKeyState.Ready);
break;
switch (privateKey.KeyType)
{
case DnssecPrivateKeyType.KeySigningKey:
privateKey.SetState(DnssecPrivateKeyState.Ready);
break;
case DnssecPrivateKeyType.ZoneSigningKey:
privateKey.SetState(DnssecPrivateKeyState.Ready);
break;
}
}
}
List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
//add DNSKEYs
List<DnsResourceRecord> dnsKeyRecords = new List<DnsResourceRecord>(_dnssecPrivateKeys.Count);
//add DNSKEYs
List<DnsResourceRecord> dnsKeyRecords = new List<DnsResourceRecord>(_dnssecPrivateKeys.Count);
foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKey in _dnssecPrivateKeys)
dnsKeyRecords.Add(new DnsResourceRecord(_name, DnsResourceRecordType.DNSKEY, DnsClass.IN, dnsKeyTtl, privateKey.Value.DnsKey));
foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKey in _dnssecPrivateKeys)
dnsKeyRecords.Add(new DnsResourceRecord(_name, DnsResourceRecordType.DNSKEY, DnsClass.IN, dnsKeyTtl, privateKey.Value.DnsKey));
if (!_entries.TryAdd(DnsResourceRecordType.DNSKEY, dnsKeyRecords))
throw new InvalidOperationException("Failed to add DNSKEY.");
if (!TrySetRecords(DnsResourceRecordType.DNSKEY, dnsKeyRecords, out IReadOnlyList<DnsResourceRecord> deletedDnsKeyRecords))
throw new InvalidOperationException("Failed to add DNSKEY.");
addedRecords.AddRange(dnsKeyRecords);
addedRecords.AddRange(dnsKeyRecords);
deletedRecords.AddRange(deletedDnsKeyRecords);
//sign all RRSets
IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
//sign all RRSets
IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
foreach (AuthZone zone in zones)
{
IReadOnlyList<DnsResourceRecord> newRRSigRecords = zone.SignAllRRSets();
if (newRRSigRecords.Count > 0)
foreach (AuthZone zone in zones)
{
zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
IReadOnlyList<DnsResourceRecord> newRRSigRecords = zone.SignAllRRSets();
if (newRRSigRecords.Count > 0)
{
zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
addedRecords.AddRange(newRRSigRecords);
deletedRecords.AddRange(deletedRRSigRecords);
addedRecords.AddRange(newRRSigRecords);
deletedRecords.AddRange(deletedRRSigRecords);
}
}
}
if (useNSec3)
{
EnableNSec3(zones, iterations, saltLength);
_dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC3;
}
else
{
EnableNSec(zones);
_dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC;
}
//update private key state
foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
{
DnssecPrivateKey privateKey = privateKeyEntry.Value;
switch (privateKey.KeyType)
if (useNSec3)
{
case DnssecPrivateKeyType.ZoneSigningKey:
privateKey.SetState(DnssecPrivateKeyState.Active);
break;
EnableNSec3(zones, iterations, saltLength);
_dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC3;
}
else
{
EnableNSec(zones);
_dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC;
}
//update private key state
foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
{
DnssecPrivateKey privateKey = privateKeyEntry.Value;
switch (privateKey.KeyType)
{
case DnssecPrivateKeyType.ZoneSigningKey:
privateKey.SetState(DnssecPrivateKeyState.Active);
break;
}
}
_dnssecTimer = new Timer(DnssecTimerCallback, null, DNSSEC_TIMER_INITIAL_INTERVAL, Timeout.Infinite);
CommitAndIncrementSerial(deletedRecords, addedRecords);
TriggerNotify();
}
catch
{
_dnssecStatus = AuthZoneDnssecStatus.Unsigned;
_dnssecPrivateKeys = null;
_dnssecTimer = new Timer(DnssecTimerCallback, null, DNSSEC_TIMER_INITIAL_INTERVAL, Timeout.Infinite);
CommitAndIncrementSerial(deletedRecords, addedRecords);
TriggerNotify();
throw;
}
}
public void UnsignZone()
@@ -865,6 +870,8 @@ namespace DnsServerCore.Dns.Zones
_dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC;
}
TriggerNotify();
}
public void ConvertToNSec3(ushort iterations, byte saltLength)
@@ -887,6 +894,8 @@ namespace DnsServerCore.Dns.Zones
_dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC3;
}
TriggerNotify();
}
public void UpdateNSec3Parameters(ushort iterations, byte saltLength)
@@ -917,6 +926,8 @@ namespace DnsServerCore.Dns.Zones
EnableNSec3(nonEmptyZones, iterations, saltLength);
}
TriggerNotify();
}
private void EnableNSec(IReadOnlyList<AuthZone> zones)
@@ -1093,10 +1104,11 @@ namespace DnsServerCore.Dns.Zones
DnsNSEC3PARAMRecord newNSec3Param = new DnsNSEC3PARAMRecord(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, iterations, salt);
DnsResourceRecord[] newNSec3ParamRecords = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NSEC3PARAM, DnsClass.IN, ttl, newNSec3Param) };
if (!_entries.TryAdd(DnsResourceRecordType.NSEC3PARAM, newNSec3ParamRecords))
if (!TrySetRecords(DnsResourceRecordType.NSEC3PARAM, newNSec3ParamRecords, out IReadOnlyList<DnsResourceRecord> deletedNSec3ParamRecords))
throw new InvalidOperationException();
addedRecords.AddRange(newNSec3ParamRecords);
deletedRecords.AddRange(deletedNSec3ParamRecords);
IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(newNSec3ParamRecords);
if (newRRSigRecords.Count > 0)
@@ -1374,7 +1386,7 @@ namespace DnsServerCore.Dns.Zones
break;
default:
throw new DnsServerException("Cannot rollover private key: the private key must be ready or active to be able to rollover.");
throw new DnsServerException("Cannot rollover private key: the private key state must be Ready or Active to be able to rollover.");
}
switch (privateKey.Algorithm)
@@ -1463,7 +1475,7 @@ namespace DnsServerCore.Dns.Zones
break;
default:
throw new DnsServerException("Cannot retire private key: the private key must be ready or active to be able to retire.");
throw new DnsServerException("Cannot retire private key: the private key state must be Ready or Active to be able to retire.");
}
}
@@ -1722,9 +1734,7 @@ namespace DnsServerCore.Dns.Zones
if (_name.Length == 0)
return privateKeys; //zone is root
DnsQuestionRecord dsQuestion = new DnsQuestionRecord(_name, DnsResourceRecordType.DS, DnsClass.IN);
DnsDatagram dsResponse = await DnsClient.RecursiveResolveAsync(dsQuestion, _dnsServer.DnsCache, _dnsServer.Proxy, _dnsServer.PreferIPv6, _dnsServer.UdpPayloadSize, _dnsServer.RandomizeName, false, false, false);
IReadOnlyList<DnsDSRecord> dsRecords = DnsClient.ParseResponseDS(dsResponse);
IReadOnlyList<DnsDSRecord> dsRecords = DnsClient.ParseResponseDS(await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(_name, DnsResourceRecordType.DS, DnsClass.IN)));
List<DnssecPrivateKey> activePrivateKeys = new List<DnssecPrivateKey>(dsRecords.Count);
@@ -1808,6 +1818,10 @@ namespace DnsServerCore.Dns.Zones
case DnsResourceRecordType.RRSIG:
throw new InvalidOperationException();
case DnsResourceRecordType.ANAME:
case DnsResourceRecordType.APP:
throw new DnsServerException("Cannot sign RRSet: The record type [" + rrsetType.ToString() + "] is not supported by DNSSEC signed primary zones.");
default:
lock (_dnssecPrivateKeys)
{
@@ -1897,7 +1911,7 @@ namespace DnsServerCore.Dns.Zones
break;
//update NSEC3 rrset for current owner name
AuthZone entZone = _dnsServer.AuthZoneManager.GetAuthZone(currentOwnerName, _name);
AuthZone entZone = _dnsServer.AuthZoneManager.GetAuthZone(_name, currentOwnerName);
if (entZone is null)
entZone = new PrimarySubDomainZone(null, currentOwnerName); //dummy empty non-terminal (ENT) sub domain object
@@ -1962,7 +1976,7 @@ namespace DnsServerCore.Dns.Zones
private void UpdateNSec3RRSetFor(AuthZone zone)
{
uint ttl = GetZoneSoaMinimum();
bool noSubDomainExistsForEmptyZone = (zone.IsEmpty || zone.HasOnlyNSec3Records()) && !_dnsServer.AuthZoneManager.SubDomainExists(zone.Name, _name);
bool noSubDomainExistsForEmptyZone = (zone.IsEmpty || zone.HasOnlyNSec3Records()) && !_dnsServer.AuthZoneManager.SubDomainExists(_name, zone.Name);
IReadOnlyList<DnsResourceRecord> newNSec3Records = GetUpdatedNSec3RRSetFor(zone, ttl, noSubDomainExistsForEmptyZone);
if (newNSec3Records.Count > 0)
@@ -2035,7 +2049,7 @@ namespace DnsServerCore.Dns.Zones
private IReadOnlyList<DnsResourceRecord> GetUpdatedNSecRRSetFor(AuthZone zone, uint ttl)
{
AuthZone nextZone = _dnsServer.AuthZoneManager.FindNextSubDomainZone(zone.Name, _name);
AuthZone nextZone = _dnsServer.AuthZoneManager.FindNextSubDomainZone(_name, zone.Name);
if (nextZone is null)
nextZone = this;
@@ -2058,7 +2072,7 @@ namespace DnsServerCore.Dns.Zones
while (true)
{
AuthZone nextZone = _dnsServer.AuthZoneManager.FindNextSubDomainZone(currentOwnerName, _name);
AuthZone nextZone = _dnsServer.AuthZoneManager.FindNextSubDomainZone(_name, currentOwnerName);
if (nextZone is null)
break;
@@ -2079,7 +2093,7 @@ namespace DnsServerCore.Dns.Zones
while (true)
{
AuthZone previousZone = _dnsServer.AuthZoneManager.FindPreviousSubDomainZone(currentOwnerName, _name);
AuthZone previousZone = _dnsServer.AuthZoneManager.FindPreviousSubDomainZone(_name, currentOwnerName);
if (previousZone is null)
break;
@@ -2102,7 +2116,7 @@ namespace DnsServerCore.Dns.Zones
if (forceGetNewRRSet)
return newNSec3Records;
AuthZone nsec3Zone = _dnsServer.AuthZoneManager.GetAuthZone(hashedOwnerName, _name);
AuthZone nsec3Zone = _dnsServer.AuthZoneManager.GetAuthZone(_name, hashedOwnerName);
if (nsec3Zone is null)
return newNSec3Records;
@@ -2111,7 +2125,7 @@ namespace DnsServerCore.Dns.Zones
private void RelinkPreviousNSecRRSetFor(DnsResourceRecord currentNSecRecord, uint ttl, bool wasRemoved)
{
AuthZone previousNsecZone = _dnsServer.AuthZoneManager.FindPreviousSubDomainZone(currentNSecRecord.Name, _name);
AuthZone previousNsecZone = _dnsServer.AuthZoneManager.FindPreviousSubDomainZone(_name, currentNSecRecord.Name);
if (previousNsecZone is null)
return; //current zone is apex
@@ -2157,7 +2171,7 @@ namespace DnsServerCore.Dns.Zones
while (true)
{
previousNSec3Zone = _dnsServer.AuthZoneManager.FindPreviousSubDomainZone(currentOwnerName, _name);
previousNSec3Zone = _dnsServer.AuthZoneManager.FindPreviousSubDomainZone(_name, currentOwnerName);
if (previousNSec3Zone is null)
break;
@@ -2181,7 +2195,7 @@ namespace DnsServerCore.Dns.Zones
while (true)
{
AuthZone nextNSec3Zone = _dnsServer.AuthZoneManager.GetAuthZone(currentOwnerName, _name);
AuthZone nextNSec3Zone = _dnsServer.AuthZoneManager.GetAuthZone(_name, currentOwnerName);
if (nextNSec3Zone is null)
break;
@@ -2280,7 +2294,7 @@ namespace DnsServerCore.Dns.Zones
break;
default:
throw new DnsServerException("Cannot update DNSKEY TTL value: one or more private keys have state other that ready or active.");
throw new DnsServerException("Cannot update DNSKEY TTL value: one or more private keys have state other than Ready or Active.");
}
}
}
@@ -2413,6 +2427,16 @@ namespace DnsServerCore.Dns.Zones
public override void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
{
if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
{
switch (type)
{
case DnsResourceRecordType.ANAME:
case DnsResourceRecordType.APP:
throw new DnsServerException("The record type is not supported by DNSSEC signed primary zones.");
}
}
switch (type)
{
case DnsResourceRecordType.CNAME:
@@ -2447,6 +2471,9 @@ namespace DnsServerCore.Dns.Zones
case DnsResourceRecordType.NSEC3:
throw new InvalidOperationException("Cannot set DNSSEC records.");
case DnsResourceRecordType.FWD:
throw new DnsServerException("The record type is not supported by primary zones.");
default:
if (records[0].OriginalTtlValue > GetZoneSoaExpire())
throw new DnsServerException("Failed to set records: TTL cannot be greater than SOA EXPIRE.");
@@ -2466,6 +2493,16 @@ namespace DnsServerCore.Dns.Zones
public override void AddRecord(DnsResourceRecord record)
{
if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
{
switch (record.Type)
{
case DnsResourceRecordType.ANAME:
case DnsResourceRecordType.APP:
throw new DnsServerException("The record type is not supported by DNSSEC signed primary zones.");
}
}
switch (record.Type)
{
case DnsResourceRecordType.APP:
@@ -2481,6 +2518,9 @@ namespace DnsServerCore.Dns.Zones
case DnsResourceRecordType.NSEC3:
throw new InvalidOperationException("Cannot add DNSSEC record.");
case DnsResourceRecordType.FWD:
throw new DnsServerException("The record type is not supported by primary zones.");
default:
if (record.OriginalTtlValue > GetZoneSoaExpire())
throw new DnsServerException("Failed to add record: TTL cannot be greater than SOA EXPIRE.");