1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100 |
- /*
- Technitium DNS Server
- Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- using DnsServerCore.Dns.Dnssec;
- using DnsServerCore.Dns.ResourceRecords;
- using DnsServerCore.Dns.ZoneManagers;
- using System;
- using System.Collections.Generic;
- using System.Security.Cryptography;
- using System.Threading;
- using System.Threading.Tasks;
- using TechnitiumLibrary.Net.Dns;
- using TechnitiumLibrary.Net.Dns.ResourceRecords;
- namespace DnsServerCore.Dns.Zones
- {
- public enum AuthZoneDnssecStatus : byte
- {
- Unsigned = 0,
- SignedWithNSEC = 1,
- SignedWithNSEC3 = 2,
- }
- //DNSSEC Operational Practices, Version 2
- //https://datatracker.ietf.org/doc/html/rfc6781
- //DNSSEC Key Rollover Timing Considerations
- //https://datatracker.ietf.org/doc/html/rfc7583
- class PrimaryZone : ApexZone
- {
- #region variables
- readonly DnsServer _dnsServer;
- readonly bool _internal;
- Dictionary<ushort, DnssecPrivateKey> _dnssecPrivateKeys;
- const uint DNSSEC_SIGNATURE_INCEPTION_OFFSET = 60 * 60;
- Timer _dnssecTimer;
- const int DNSSEC_TIMER_INITIAL_INTERVAL = 30000;
- const int DNSSEC_TIMER_PERIODIC_INTERVAL = 900000;
- DateTime _lastSignatureRefreshCheckedOn;
- readonly object _dnssecUpdateLock = new object();
- #endregion
- #region constructor
- public PrimaryZone(DnsServer dnsServer, AuthZoneInfo zoneInfo)
- : base(zoneInfo)
- {
- _dnsServer = dnsServer;
- IReadOnlyCollection<DnssecPrivateKey> dnssecPrivateKeys = zoneInfo.DnssecPrivateKeys;
- if (dnssecPrivateKeys is not null)
- {
- _dnssecPrivateKeys = new Dictionary<ushort, DnssecPrivateKey>(dnssecPrivateKeys.Count);
- foreach (DnssecPrivateKey dnssecPrivateKey in dnssecPrivateKeys)
- _dnssecPrivateKeys.Add(dnssecPrivateKey.KeyTag, dnssecPrivateKey);
- }
- InitNotify(_dnsServer);
- }
- public PrimaryZone(DnsServer dnsServer, string name, string primaryNameServer, bool @internal)
- : base(name)
- {
- _dnsServer = dnsServer;
- _internal = @internal;
- if (_internal)
- {
- _zoneTransfer = AuthZoneTransfer.Deny;
- _notify = AuthZoneNotify.None;
- _update = AuthZoneUpdate.Deny;
- }
- else
- {
- _zoneTransfer = AuthZoneTransfer.AllowOnlyZoneNameServers;
- _notify = AuthZoneNotify.ZoneNameServers;
- _update = AuthZoneUpdate.Deny;
- InitNotify(_dnsServer);
- }
- DnsSOARecordData soa = new DnsSOARecordData(primaryNameServer, _name.Length == 0 ? "hostadmin@localhost" : "hostadmin@" + _name, 1, 900, 300, 604800, 900);
- _entries[DnsResourceRecordType.SOA] = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, soa.Minimum, soa) };
- _entries[DnsResourceRecordType.NS] = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NS, DnsClass.IN, 3600, new DnsNSRecordData(soa.PrimaryNameServer)) };
- }
- internal PrimaryZone(DnsServer dnsServer, string name, DnsSOARecordData soa, DnsNSRecordData ns)
- : base(name)
- {
- _dnsServer = dnsServer;
- _internal = true;
- _zoneTransfer = AuthZoneTransfer.Deny;
- _notify = AuthZoneNotify.None;
- _update = AuthZoneUpdate.Deny;
- _entries[DnsResourceRecordType.SOA] = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, soa.Minimum, soa) };
- _entries[DnsResourceRecordType.NS] = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NS, DnsClass.IN, 3600, ns) };
- }
- #endregion
- #region IDisposable
- bool _disposed;
- protected override void Dispose(bool disposing)
- {
- try
- {
- if (_disposed)
- return;
- if (disposing)
- {
- Timer dnssecTimer = _dnssecTimer;
- if (dnssecTimer is not null)
- {
- lock (dnssecTimer)
- {
- dnssecTimer.Dispose();
- _dnssecTimer = null;
- }
- }
- }
- _disposed = true;
- }
- finally
- {
- base.Dispose(disposing);
- }
- }
- #endregion
- #region DNSSEC
- internal override void UpdateDnssecStatus()
- {
- base.UpdateDnssecStatus();
- if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
- _dnssecTimer = new Timer(DnssecTimerCallback, null, DNSSEC_TIMER_INITIAL_INTERVAL, Timeout.Infinite);
- }
- private async void DnssecTimerCallback(object state)
- {
- try
- {
- List<DnssecPrivateKey> kskToReady = null;
- List<DnssecPrivateKey> kskToActivate = null;
- List<DnssecPrivateKey> kskToRetire = null;
- List<DnssecPrivateKey> kskToRevoke = null;
- List<DnssecPrivateKey> zskToActivate = null;
- List<DnssecPrivateKey> zskToRetire = null;
- List<DnssecPrivateKey> zskToRollover = null;
- List<DnssecPrivateKey> keysToUnpublish = null;
- bool saveZone = false;
- lock (_dnssecPrivateKeys)
- {
- foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
- {
- DnssecPrivateKey privateKey = privateKeyEntry.Value;
- if (privateKey.KeyType == DnssecPrivateKeyType.KeySigningKey)
- {
- //KSK
- switch (privateKey.State)
- {
- case DnssecPrivateKeyState.Published:
- if (DateTime.UtcNow > GetDnsKeyStateReadyOn(privateKey))
- {
- //long enough time for old RRsets to expire from caches
- if (kskToReady is null)
- kskToReady = new List<DnssecPrivateKey>();
- kskToReady.Add(privateKey);
- }
- break;
- case DnssecPrivateKeyState.Ready:
- if (privateKey.IsRetiring)
- {
- if (kskToRetire is null)
- kskToRetire = new List<DnssecPrivateKey>();
- kskToRetire.Add(privateKey);
- }
- else
- {
- if (kskToActivate is null)
- kskToActivate = new List<DnssecPrivateKey>();
- kskToActivate.Add(privateKey);
- }
- break;
- case DnssecPrivateKeyState.Active:
- if (privateKey.IsRetiring)
- {
- if (kskToRetire is null)
- kskToRetire = new List<DnssecPrivateKey>();
- kskToRetire.Add(privateKey);
- }
- break;
- case DnssecPrivateKeyState.Retired:
- //KSK needs to be revoked for RFC5011 consideration
- if (kskToRevoke is null)
- kskToRevoke = new List<DnssecPrivateKey>();
- kskToRevoke.Add(privateKey);
- break;
- case DnssecPrivateKeyState.Revoked:
- //rfc7583#section-3.3.4
- //modifiedQueryInterval = MAX(1hr, MIN(15 days, TTLkey / 2))
- uint modifiedQueryInterval = Math.Max(3600u, Math.Min(15 * 24 * 60 * 60, GetDnsKeyTtl() / 2));
- if (DateTime.UtcNow > privateKey.StateChangedOn.AddSeconds(modifiedQueryInterval))
- {
- //key has been revoked for sufficient time
- if (keysToUnpublish is null)
- keysToUnpublish = new List<DnssecPrivateKey>();
- keysToUnpublish.Add(privateKey);
- }
- break;
- }
- }
- else
- {
- //ZSK
- switch (privateKey.State)
- {
- case DnssecPrivateKeyState.Published:
- if (DateTime.UtcNow > privateKey.StateChangedOn.AddSeconds(GetDnsKeyTtl() + GetPropagationDelay()))
- {
- //long enough time old RRset to expire from caches
- privateKey.SetState(DnssecPrivateKeyState.Ready);
- if (zskToActivate is null)
- zskToActivate = new List<DnssecPrivateKey>();
- zskToActivate.Add(privateKey);
- }
- break;
- case DnssecPrivateKeyState.Ready:
- if (zskToActivate is null)
- zskToActivate = new List<DnssecPrivateKey>();
- zskToActivate.Add(privateKey);
- break;
- case DnssecPrivateKeyState.Active:
- if (privateKey.IsRetiring)
- {
- if (zskToRetire is null)
- zskToRetire = new List<DnssecPrivateKey>();
- zskToRetire.Add(privateKey);
- }
- else
- {
- if (privateKey.IsRolloverNeeded())
- {
- if (zskToRollover is null)
- zskToRollover = new List<DnssecPrivateKey>();
- zskToRollover.Add(privateKey);
- }
- }
- break;
- case DnssecPrivateKeyState.Retired:
- if (DateTime.UtcNow > privateKey.StateChangedOn.AddSeconds(GetMaxRRSigTtl() + GetPropagationDelay()))
- {
- //key has been retired for sufficient time
- if (keysToUnpublish is null)
- keysToUnpublish = new List<DnssecPrivateKey>();
- keysToUnpublish.Add(privateKey);
- }
- break;
- }
- }
- }
- }
- #region KSK actions
- if (kskToReady is not null)
- {
- string dnsKeyTags = null;
- foreach (DnssecPrivateKey kskPrivateKey in kskToReady)
- {
- kskPrivateKey.SetState(DnssecPrivateKeyState.Ready);
- if (kskToActivate is null)
- kskToActivate = new List<DnssecPrivateKey>();
- kskToActivate.Add(kskPrivateKey);
- if (dnsKeyTags is null)
- dnsKeyTags = kskPrivateKey.KeyTag.ToString();
- else
- dnsKeyTags += ", " + kskPrivateKey.KeyTag.ToString();
- }
- saveZone = true;
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write("The KSK DNSKEYs (" + dnsKeyTags + ") from the primary zone are ready for changing the DS records at the parent zone: " + _name);
- }
- if (kskToActivate is not null)
- {
- try
- {
- IReadOnlyList<DnssecPrivateKey> kskPrivateKeys = await GetDSPublishedPrivateKeys(kskToActivate);
- if (kskPrivateKeys.Count > 0)
- {
- string dnsKeyTags = null;
- foreach (DnssecPrivateKey kskPrivateKey in kskPrivateKeys)
- {
- kskPrivateKey.SetState(DnssecPrivateKeyState.Active);
- if (dnsKeyTags is null)
- dnsKeyTags = kskPrivateKey.KeyTag.ToString();
- else
- dnsKeyTags += ", " + kskPrivateKey.KeyTag.ToString();
- }
- saveZone = true;
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write("The KSK DNSKEYs (" + dnsKeyTags + ") from the primary zone were activated successfully: " + _name);
- }
- }
- catch (Exception ex)
- {
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write(ex);
- }
- }
- if (kskToRetire is not null)
- saveZone = RetireKskDnsKeys(kskToRetire, false);
- if (kskToRevoke is not null)
- {
- uint dsTtl = await GetDSTtl();
- uint parentSidePropagationDelay = await GetParentSidePropagationDelayAsync();
- List<DnssecPrivateKey> revokeKskPrivateKeys = null;
- foreach (DnssecPrivateKey privateKey in kskToRevoke)
- {
- if (DateTime.UtcNow > privateKey.StateChangedOn.AddSeconds(dsTtl + parentSidePropagationDelay))
- {
- if (revokeKskPrivateKeys is null)
- revokeKskPrivateKeys = new List<DnssecPrivateKey>();
- revokeKskPrivateKeys.Add(privateKey);
- }
- }
- if (revokeKskPrivateKeys is not null)
- {
- RevokeKskDnsKeys(revokeKskPrivateKeys);
- saveZone = true;
- }
- }
- #endregion
- #region ZSK actions
- if (zskToActivate is not null)
- {
- ActivateZskDnsKeys(zskToActivate);
- saveZone = true;
- }
- if (zskToRetire is not null)
- saveZone = RetireZskDnsKeys(zskToRetire, false);
- if (zskToRollover is not null)
- {
- foreach (DnssecPrivateKey zskPrivateKey in zskToRollover)
- RolloverDnsKey(zskPrivateKey.KeyTag);
- saveZone = true;
- }
- #endregion
- if (keysToUnpublish is not null)
- {
- UnpublishDnsKeys(keysToUnpublish);
- saveZone = true;
- }
- //re-signing task
- uint reSignPeriod = GetSignatureValidityPeriod() / 10; //the period when signature refresh check is done
- if (DateTime.UtcNow > _lastSignatureRefreshCheckedOn.AddSeconds(reSignPeriod))
- {
- if (TryRefreshAllSignatures())
- saveZone = true;
- _lastSignatureRefreshCheckedOn = DateTime.UtcNow;
- }
- if (saveZone)
- _dnsServer.AuthZoneManager.SaveZoneFile(_name);
- }
- catch (Exception ex)
- {
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write(ex);
- }
- finally
- {
- Timer dnssecTimer = _dnssecTimer;
- if (dnssecTimer is not null)
- {
- lock (dnssecTimer)
- {
- dnssecTimer.Change(DNSSEC_TIMER_PERIODIC_INTERVAL, Timeout.Infinite);
- }
- }
- }
- }
- public void SignZoneWithRsaNSec(string hashAlgorithm, int kskKeySize, int zskKeySize, uint dnsKeyTtl, ushort zskRolloverDays)
- {
- SignZoneWithRsa(hashAlgorithm, kskKeySize, zskKeySize, false, 0, 0, dnsKeyTtl, zskRolloverDays);
- }
- public void SignZoneWithRsaNSec3(string hashAlgorithm, int kskKeySize, int zskKeySize, ushort iterations, byte saltLength, uint dnsKeyTtl, ushort zskRolloverDays)
- {
- SignZoneWithRsa(hashAlgorithm, kskKeySize, zskKeySize, true, iterations, saltLength, dnsKeyTtl, zskRolloverDays);
- }
- private void SignZoneWithRsa(string hashAlgorithm, int kskKeySize, int zskKeySize, bool useNSec3, ushort iterations, byte saltLength, uint dnsKeyTtl, ushort zskRolloverDays)
- {
- if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
- 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");
- if (saltLength > 32)
- throw new ArgumentOutOfRangeException(nameof(saltLength), "NSEC3 salt length valid range is 0-32");
- //generate private keys
- DnssecAlgorithm algorithm;
- switch (hashAlgorithm.ToUpper())
- {
- case "MD5":
- algorithm = DnssecAlgorithm.RSAMD5;
- break;
- case "SHA1":
- algorithm = DnssecAlgorithm.RSASHA1;
- break;
- case "SHA256":
- algorithm = DnssecAlgorithm.RSASHA256;
- break;
- case "SHA512":
- algorithm = DnssecAlgorithm.RSASHA512;
- break;
- default:
- throw new NotSupportedException("Hash algorithm is not supported: " + hashAlgorithm);
- }
- DnssecPrivateKey kskPrivateKey = DnssecPrivateKey.Create(algorithm, DnssecPrivateKeyType.KeySigningKey, kskKeySize);
- DnssecPrivateKey zskPrivateKey = DnssecPrivateKey.Create(algorithm, DnssecPrivateKeyType.ZoneSigningKey, zskKeySize);
- zskPrivateKey.RolloverDays = zskRolloverDays;
- _dnssecPrivateKeys = new Dictionary<ushort, DnssecPrivateKey>(4);
- _dnssecPrivateKeys.Add(kskPrivateKey.KeyTag, kskPrivateKey);
- _dnssecPrivateKeys.Add(zskPrivateKey.KeyTag, zskPrivateKey);
- //sign zone
- SignZone(useNSec3, iterations, saltLength, dnsKeyTtl);
- }
- public void SignZoneWithEcdsaNSec(string curve, uint dnsKeyTtl, ushort zskRolloverDays)
- {
- SignZoneWithEcdsa(curve, false, 0, 0, dnsKeyTtl, zskRolloverDays);
- }
- public void SignZoneWithEcdsaNSec3(string curve, ushort iterations, byte saltLength, uint dnsKeyTtl, ushort zskRolloverDays)
- {
- SignZoneWithEcdsa(curve, true, iterations, saltLength, dnsKeyTtl, zskRolloverDays);
- }
- private void SignZoneWithEcdsa(string curve, bool useNSec3, ushort iterations, byte saltLength, uint dnsKeyTtl, ushort zskRolloverDays)
- {
- if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
- 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");
- if (saltLength > 32)
- throw new ArgumentOutOfRangeException(nameof(saltLength), "NSEC3 salt length valid range is 0-32");
- //generate private keys
- DnssecAlgorithm algorithm;
- switch (curve.ToUpper())
- {
- case "P256":
- algorithm = DnssecAlgorithm.ECDSAP256SHA256;
- break;
- case "P384":
- algorithm = DnssecAlgorithm.ECDSAP384SHA384;
- break;
- default:
- throw new NotSupportedException("ECDSA curve is not supported: " + curve);
- }
- DnssecPrivateKey kskPrivateKey = DnssecPrivateKey.Create(algorithm, DnssecPrivateKeyType.KeySigningKey);
- DnssecPrivateKey zskPrivateKey = DnssecPrivateKey.Create(algorithm, DnssecPrivateKeyType.ZoneSigningKey);
- zskPrivateKey.RolloverDays = zskRolloverDays;
- _dnssecPrivateKeys = new Dictionary<ushort, DnssecPrivateKey>(4);
- _dnssecPrivateKeys.Add(kskPrivateKey.KeyTag, kskPrivateKey);
- _dnssecPrivateKeys.Add(zskPrivateKey.KeyTag, zskPrivateKey);
- //sign zone
- SignZone(useNSec3, iterations, saltLength, dnsKeyTtl);
- }
- private void SignZone(bool useNSec3, ushort iterations, byte saltLength, uint dnsKeyTtl)
- {
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- try
- {
- //update private key state
- foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
- {
- DnssecPrivateKey privateKey = privateKeyEntry.Value;
- switch (privateKey.KeyType)
- {
- case DnssecPrivateKeyType.KeySigningKey:
- privateKey.SetState(DnssecPrivateKeyState.Published);
- break;
- case DnssecPrivateKeyType.ZoneSigningKey:
- privateKey.SetState(DnssecPrivateKeyState.Ready);
- break;
- }
- }
- //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));
- if (!TrySetRecords(DnsResourceRecordType.DNSKEY, dnsKeyRecords, out IReadOnlyList<DnsResourceRecord> deletedDnsKeyRecords))
- throw new InvalidOperationException("Failed to add DNSKEY.");
- addedRecords.AddRange(dnsKeyRecords);
- deletedRecords.AddRange(deletedDnsKeyRecords);
- //sign all RRSets
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- foreach (AuthZone zone in zones)
- {
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = zone.SignAllRRSets();
- if (newRRSigRecords.Count > 0)
- {
- zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> 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)
- {
- 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;
- foreach (DnsResourceRecord addedRecord in addedRecords)
- TryDeleteRecord(addedRecord.Type, addedRecord.RDATA, out _);
- foreach (DnsResourceRecord deletedRecord in deletedRecords)
- AddRecord(deletedRecord, out _, out _);
- throw;
- }
- }
- public void UnsignZone()
- {
- if (_dnssecStatus == AuthZoneDnssecStatus.Unsigned)
- throw new DnsServerException("Cannot unsign zone: the is zone not signed.");
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- foreach (AuthZone zone in zones)
- {
- deletedRecords.AddRange(zone.RemoveAllDnssecRecords());
- if (zone is SubDomainZone subDomainZone)
- {
- if (zone.IsEmpty)
- _dnsServer.AuthZoneManager.RemoveSubDomainZone(zone.Name); //remove empty sub zone
- else
- subDomainZone.AutoUpdateState();
- }
- }
- Timer dnssecTimer = _dnssecTimer;
- if (dnssecTimer is not null)
- {
- lock (dnssecTimer)
- {
- dnssecTimer.Dispose();
- _dnssecTimer = null;
- }
- }
- _dnssecPrivateKeys = null;
- _dnssecStatus = AuthZoneDnssecStatus.Unsigned;
- CommitAndIncrementSerial(deletedRecords);
- TriggerNotify();
- }
- public void ConvertToNSec()
- {
- if (_dnssecStatus != AuthZoneDnssecStatus.SignedWithNSEC3)
- throw new DnsServerException("Cannot convert to NSEC: the zone must be signed with NSEC3 for conversion.");
- lock (_dnssecUpdateLock)
- {
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- DisableNSec3(zones);
- //since zones were removed when disabling NSEC3; get updated non empty zones list
- List<AuthZone> nonEmptyZones = new List<AuthZone>(zones.Count);
- foreach (AuthZone zone in zones)
- {
- if (!zone.IsEmpty)
- nonEmptyZones.Add(zone);
- }
- EnableNSec(nonEmptyZones);
- _dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC;
- }
- TriggerNotify();
- }
- public void ConvertToNSec3(ushort iterations, byte saltLength)
- {
- if (_dnssecStatus != AuthZoneDnssecStatus.SignedWithNSEC)
- throw new DnsServerException("Cannot convert to NSEC3: the zone must be signed with NSEC for conversion.");
- if (iterations > 50)
- throw new ArgumentOutOfRangeException(nameof(iterations), "NSEC3 iterations valid range is 0-50");
- if (saltLength > 32)
- throw new ArgumentOutOfRangeException(nameof(saltLength), "NSEC3 salt length valid range is 0-32");
- lock (_dnssecUpdateLock)
- {
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- DisableNSec(zones);
- EnableNSec3(zones, iterations, saltLength);
- _dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC3;
- }
- TriggerNotify();
- }
- public void UpdateNSec3Parameters(ushort iterations, byte saltLength)
- {
- if (_dnssecStatus != AuthZoneDnssecStatus.SignedWithNSEC3)
- throw new DnsServerException("Cannot update NSEC3 parameters: the zone must be signed with NSEC3 first.");
- if (iterations > 50)
- throw new ArgumentOutOfRangeException(nameof(iterations), "NSEC3 iterations valid range is 0-50");
- if (saltLength > 32)
- throw new ArgumentOutOfRangeException(nameof(saltLength), "NSEC3 salt length valid range is 0-32");
- lock (_dnssecUpdateLock)
- {
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- DisableNSec3(zones);
- //since zones were removed when disabling NSEC3; get updated non empty zones list
- List<AuthZone> nonEmptyZones = new List<AuthZone>(zones.Count);
- foreach (AuthZone zone in zones)
- {
- if (!zone.IsEmpty)
- nonEmptyZones.Add(zone);
- }
- EnableNSec3(nonEmptyZones, iterations, saltLength);
- }
- TriggerNotify();
- }
- private void RefreshNSec()
- {
- lock (_dnssecUpdateLock)
- {
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- EnableNSec(zones);
- }
- }
- private void RefreshNSec3()
- {
- lock (_dnssecUpdateLock)
- {
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- //get non NSEC3 zones
- List<AuthZone> nonNSec3Zones = new List<AuthZone>(zones.Count);
- foreach (AuthZone zone in zones)
- {
- if (zone.HasOnlyNSec3Records())
- continue;
- nonNSec3Zones.Add(zone);
- }
- IReadOnlyList<DnsResourceRecord> nsec3ParamRecords = GetRecords(DnsResourceRecordType.NSEC3PARAM);
- DnsNSEC3PARAMRecordData nsec3Param = nsec3ParamRecords[0].RDATA as DnsNSEC3PARAMRecordData;
- EnableNSec3(nonNSec3Zones, nsec3Param.Iterations, nsec3Param.Salt);
- }
- }
- private void EnableNSec(IReadOnlyList<AuthZone> zones)
- {
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- uint ttl = GetZoneSoaMinimum();
- for (int i = 0; i < zones.Count; i++)
- {
- AuthZone zone = zones[i];
- AuthZone nextZone;
- if (i < zones.Count - 1)
- nextZone = zones[i + 1];
- else
- nextZone = zones[0];
- IReadOnlyList<DnsResourceRecord> newNSecRecords = zone.GetUpdatedNSecRRSet(nextZone.Name, ttl);
- if (newNSecRecords.Count > 0)
- {
- if (!zone.TrySetRecords(DnsResourceRecordType.NSEC, newNSecRecords, out IReadOnlyList<DnsResourceRecord> deletedNSecRecords))
- throw new DnsServerException("Failed to set DNSSEC records. Please try again.");
- addedRecords.AddRange(newNSecRecords);
- deletedRecords.AddRange(deletedNSecRecords);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(newNSecRecords);
- if (newRRSigRecords.Count > 0)
- {
- zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- }
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- }
- private void DisableNSec(IReadOnlyList<AuthZone> zones)
- {
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- foreach (AuthZone zone in zones)
- deletedRecords.AddRange(zone.RemoveNSecRecordsWithRRSig());
- CommitAndIncrementSerial(deletedRecords);
- }
- private void EnableNSec3(IReadOnlyList<AuthZone> zones, ushort iterations, byte saltLength)
- {
- byte[] salt;
- if (saltLength > 0)
- {
- salt = new byte[saltLength];
- using RandomNumberGenerator rng = RandomNumberGenerator.Create();
- rng.GetBytes(salt);
- }
- else
- {
- salt = Array.Empty<byte>();
- }
- EnableNSec3(zones, iterations, salt);
- }
- private void EnableNSec3(IReadOnlyList<AuthZone> zones, ushort iterations, byte[] salt)
- {
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> partialNSec3Records = new List<DnsResourceRecord>(zones.Count);
- int apexLabelCount = DnsRRSIGRecordData.GetLabelCount(_name);
- uint ttl = GetZoneSoaMinimum();
- //list all partial NSEC3 records
- foreach (AuthZone zone in zones)
- {
- partialNSec3Records.Add(zone.GetPartialNSec3Record(_name, ttl, iterations, salt));
- int zoneLabelCount = DnsRRSIGRecordData.GetLabelCount(zone.Name);
- if ((zoneLabelCount - apexLabelCount) > 1)
- {
- //empty non-terminal (ENT) may exists
- string currentOwnerName = zone.Name;
- while (true)
- {
- currentOwnerName = AuthZoneManager.GetParentZone(currentOwnerName);
- if (currentOwnerName.Equals(_name, StringComparison.OrdinalIgnoreCase))
- break;
- //add partial NSEC3 record for ENT
- AuthZone entZone = new PrimarySubDomainZone(null, currentOwnerName); //dummy empty non-terminal (ENT) sub domain object
- partialNSec3Records.Add(entZone.GetPartialNSec3Record(_name, ttl, iterations, salt));
- }
- }
- }
- //sort partial NSEC3 records
- partialNSec3Records.Sort(delegate (DnsResourceRecord rr1, DnsResourceRecord rr2)
- {
- return string.CompareOrdinal(rr1.Name, rr2.Name);
- });
- //deduplicate partial NSEC3 records and insert next hashed owner name to complete them
- List<DnsResourceRecord> uniqueNSec3Records = new List<DnsResourceRecord>(partialNSec3Records.Count);
- for (int i = 0; i < partialNSec3Records.Count; i++)
- {
- DnsResourceRecord partialNSec3Record = partialNSec3Records[i];
- DnsResourceRecord nextPartialNSec3Record;
- if (i < partialNSec3Records.Count - 1)
- {
- nextPartialNSec3Record = partialNSec3Records[i + 1];
- //check for duplicates
- if (partialNSec3Record.Name.Equals(nextPartialNSec3Record.Name, StringComparison.OrdinalIgnoreCase))
- {
- //found duplicate; merge current nsec3 into next nsec3
- DnsNSEC3RecordData nsec3 = partialNSec3Record.RDATA as DnsNSEC3RecordData;
- DnsNSEC3RecordData nextNSec3 = nextPartialNSec3Record.RDATA as DnsNSEC3RecordData;
- List<DnsResourceRecordType> uniqueTypes = new List<DnsResourceRecordType>(nsec3.Types.Count + nextNSec3.Types.Count);
- uniqueTypes.AddRange(nsec3.Types);
- foreach (DnsResourceRecordType type in nextNSec3.Types)
- {
- if (!uniqueTypes.Contains(type))
- uniqueTypes.Add(type);
- }
- uniqueTypes.Sort();
- //update the next nsec3 record and continue
- DnsNSEC3RecordData mergedPartialNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, iterations, salt, Array.Empty<byte>(), uniqueTypes);
- partialNSec3Records[i + 1] = new DnsResourceRecord(partialNSec3Record.Name, DnsResourceRecordType.NSEC3, DnsClass.IN, ttl, mergedPartialNSec3);
- continue;
- }
- }
- else
- {
- //for last NSEC3, next NSEC3 is the first in list
- nextPartialNSec3Record = partialNSec3Records[0];
- }
- //add NSEC3 record with next hashed owner name
- {
- DnsNSEC3RecordData partialNSec3 = partialNSec3Record.RDATA as DnsNSEC3RecordData;
- byte[] nextHashedOwnerName = DnsNSEC3RecordData.GetHashedOwnerNameFrom(nextPartialNSec3Record.Name);
- DnsNSEC3RecordData updatedNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, iterations, salt, nextHashedOwnerName, partialNSec3.Types);
- uniqueNSec3Records.Add(new DnsResourceRecord(partialNSec3Record.Name, DnsResourceRecordType.NSEC3, DnsClass.IN, ttl, updatedNSec3));
- }
- }
- //insert and sign NSEC3 records
- foreach (DnsResourceRecord uniqueNSec3Record in uniqueNSec3Records)
- {
- AuthZone zone = _dnsServer.AuthZoneManager.GetOrAddSubDomainZone(_name, uniqueNSec3Record.Name);
- DnsResourceRecord[] newNSec3Records = new DnsResourceRecord[] { uniqueNSec3Record };
- if (!zone.TrySetRecords(DnsResourceRecordType.NSEC3, newNSec3Records, out IReadOnlyList<DnsResourceRecord> deletedNSec3Records))
- throw new InvalidOperationException();
- addedRecords.AddRange(newNSec3Records);
- deletedRecords.AddRange(deletedNSec3Records);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(newNSec3Records);
- if (newRRSigRecords.Count > 0)
- {
- zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- }
- //insert and sign NSEC3PARAM record
- {
- DnsNSEC3PARAMRecordData newNSec3Param = new DnsNSEC3PARAMRecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, iterations, salt);
- DnsResourceRecord[] newNSec3ParamRecords = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NSEC3PARAM, DnsClass.IN, ttl, newNSec3Param) };
- 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)
- {
- AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- }
- private void DisableNSec3(IReadOnlyList<AuthZone> zones)
- {
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- foreach (AuthZone zone in zones)
- {
- deletedRecords.AddRange(zone.RemoveNSec3RecordsWithRRSig());
- if (zone is SubDomainZone subDomainZone)
- {
- if (zone.IsEmpty)
- _dnsServer.AuthZoneManager.RemoveSubDomainZone(zone.Name); //remove empty sub zone
- else
- subDomainZone.AutoUpdateState();
- }
- }
- CommitAndIncrementSerial(deletedRecords);
- }
- public void GenerateAndAddRsaKey(DnssecPrivateKeyType keyType, string hashAlgorithm, int keySize, ushort rolloverDays)
- {
- if (_dnssecStatus == AuthZoneDnssecStatus.Unsigned)
- throw new DnsServerException("The zone must be signed.");
- DnssecAlgorithm algorithm;
- switch (hashAlgorithm.ToUpper())
- {
- case "MD5":
- algorithm = DnssecAlgorithm.RSAMD5;
- break;
- case "SHA1":
- algorithm = DnssecAlgorithm.RSASHA1;
- break;
- case "SHA256":
- algorithm = DnssecAlgorithm.RSASHA256;
- break;
- case "SHA512":
- algorithm = DnssecAlgorithm.RSASHA512;
- break;
- default:
- throw new NotSupportedException("Hash algorithm is not supported: " + hashAlgorithm);
- }
- GenerateAndAddRsaKey(keyType, algorithm, keySize, rolloverDays);
- }
- private void GenerateAndAddRsaKey(DnssecPrivateKeyType keyType, DnssecAlgorithm algorithm, int keySize, ushort rolloverDays)
- {
- int i = 0;
- while (i++ < 5)
- {
- DnssecPrivateKey privateKey = DnssecPrivateKey.Create(algorithm, keyType, keySize);
- privateKey.RolloverDays = rolloverDays;
- lock (_dnssecPrivateKeys)
- {
- if (_dnssecPrivateKeys.TryAdd(privateKey.KeyTag, privateKey))
- return;
- }
- }
- throw new DnsServerException("Failed to add private key: key tag collision.");
- }
- public void GenerateAndAddEcdsaKey(DnssecPrivateKeyType keyType, string curve, ushort rolloverDays)
- {
- if (_dnssecStatus == AuthZoneDnssecStatus.Unsigned)
- throw new DnsServerException("The zone must be signed.");
- DnssecAlgorithm algorithm;
- switch (curve.ToUpper())
- {
- case "P256":
- algorithm = DnssecAlgorithm.ECDSAP256SHA256;
- break;
- case "P384":
- algorithm = DnssecAlgorithm.ECDSAP384SHA384;
- break;
- default:
- throw new NotSupportedException("ECDSA curve is not supported: " + curve);
- }
- GenerateAndAddEcdsaKey(keyType, algorithm, rolloverDays);
- }
- private void GenerateAndAddEcdsaKey(DnssecPrivateKeyType keyType, DnssecAlgorithm algorithm, ushort rolloverDays)
- {
- int i = 0;
- while (i++ < 5)
- {
- DnssecPrivateKey privateKey = DnssecPrivateKey.Create(algorithm, keyType);
- privateKey.RolloverDays = rolloverDays;
- lock (_dnssecPrivateKeys)
- {
- if (_dnssecPrivateKeys.TryAdd(privateKey.KeyTag, privateKey))
- return;
- }
- }
- throw new DnsServerException("Failed to add private key: key tag collision.");
- }
- public void UpdatePrivateKey(ushort keyTag, ushort rolloverDays)
- {
- lock (_dnssecPrivateKeys)
- {
- if (!_dnssecPrivateKeys.TryGetValue(keyTag, out DnssecPrivateKey privateKey))
- throw new DnsServerException("Cannot update private key: no such private key was found.");
- privateKey.RolloverDays = rolloverDays;
- }
- }
- public void DeletePrivateKey(ushort keyTag)
- {
- if (_dnssecStatus == AuthZoneDnssecStatus.Unsigned)
- throw new DnsServerException("The zone must be signed.");
- lock (_dnssecPrivateKeys)
- {
- if (!_dnssecPrivateKeys.TryGetValue(keyTag, out DnssecPrivateKey privateKey))
- throw new DnsServerException("Cannot delete private key: no such private key was found.");
- if (privateKey.State != DnssecPrivateKeyState.Generated)
- throw new DnsServerException("Cannot delete private key: only keys with Generated state can be deleted.");
- _dnssecPrivateKeys.Remove(keyTag);
- }
- }
- public void PublishAllGeneratedKeys()
- {
- if (_dnssecStatus == AuthZoneDnssecStatus.Unsigned)
- throw new DnsServerException("The zone must be signed.");
- List<DnssecPrivateKey> generatedPrivateKeys = new List<DnssecPrivateKey>();
- List<DnsResourceRecord> newDnsKeyRecords = new List<DnsResourceRecord>();
- uint dnsKeyTtl = GetDnsKeyTtl();
- lock (_dnssecPrivateKeys)
- {
- foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
- {
- DnssecPrivateKey privateKey = privateKeyEntry.Value;
- if (privateKey.State == DnssecPrivateKeyState.Generated)
- {
- generatedPrivateKeys.Add(privateKey);
- newDnsKeyRecords.Add(new DnsResourceRecord(_name, DnsResourceRecordType.DNSKEY, DnsClass.IN, dnsKeyTtl, privateKey.DnsKey));
- }
- }
- }
- if (generatedPrivateKeys.Count == 0)
- throw new DnsServerException("Cannot publish DNSKEY: no generated private keys were found.");
- IReadOnlyList<DnsResourceRecord> dnsKeyRecords = _entries.AddOrUpdate(DnsResourceRecordType.DNSKEY, delegate (DnsResourceRecordType key)
- {
- return newDnsKeyRecords;
- },
- delegate (DnsResourceRecordType key, IReadOnlyList<DnsResourceRecord> existingRecords)
- {
- foreach (DnsResourceRecord existingRecord in existingRecords)
- {
- foreach (DnsResourceRecord newDnsKeyRecord in newDnsKeyRecords)
- {
- if (existingRecord.Equals(newDnsKeyRecord))
- throw new DnsServerException("Cannot publish DNSKEY: the key is already published.");
- }
- }
- List<DnsResourceRecord> dnsKeyRecords = new List<DnsResourceRecord>(existingRecords.Count + newDnsKeyRecords.Count);
- dnsKeyRecords.AddRange(existingRecords);
- dnsKeyRecords.AddRange(newDnsKeyRecords);
- return dnsKeyRecords;
- });
- //update private key state before signing
- foreach (DnssecPrivateKey privateKey in generatedPrivateKeys)
- privateKey.SetState(DnssecPrivateKeyState.Published);
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- addedRecords.AddRange(newDnsKeyRecords);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(dnsKeyRecords);
- if (newRRSigRecords.Count > 0)
- {
- AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- TriggerNotify();
- }
- private void ActivateZskDnsKeys(IReadOnlyList<DnssecPrivateKey> zskPrivateKeys)
- {
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- //re-sign all records with new private keys
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- foreach (AuthZone zone in zones)
- {
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = zone.SignAllRRSets();
- if (newRRSigRecords.Count > 0)
- {
- zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- TriggerNotify();
- //update private key state
- string dnsKeyTags = null;
- foreach (DnssecPrivateKey privateKey in zskPrivateKeys)
- {
- privateKey.SetState(DnssecPrivateKeyState.Active);
- if (dnsKeyTags is null)
- dnsKeyTags = privateKey.KeyTag.ToString();
- else
- dnsKeyTags += ", " + privateKey.KeyTag.ToString();
- }
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write("The ZSK DNSKEYs (" + dnsKeyTags + ") from the primary zone were activated successfully: " + _name);
- }
- public void RolloverDnsKey(ushort keyTag)
- {
- if (_dnssecStatus == AuthZoneDnssecStatus.Unsigned)
- throw new DnsServerException("The zone must be signed.");
- DnssecPrivateKey privateKey;
- lock (_dnssecPrivateKeys)
- {
- if (!_dnssecPrivateKeys.TryGetValue(keyTag, out privateKey))
- throw new DnsServerException("Cannot rollover private key: no such private key was found.");
- }
- switch (privateKey.State)
- {
- case DnssecPrivateKeyState.Ready:
- case DnssecPrivateKeyState.Active:
- if (privateKey.IsRetiring)
- throw new DnsServerException("Cannot rollover private key: the private key is already set to retire.");
- break;
- default:
- throw new DnsServerException("Cannot rollover private key: the private key state must be Ready or Active to be able to rollover.");
- }
- switch (privateKey.Algorithm)
- {
- case DnssecAlgorithm.RSAMD5:
- case DnssecAlgorithm.RSASHA1:
- case DnssecAlgorithm.RSASHA1_NSEC3_SHA1:
- case DnssecAlgorithm.RSASHA256:
- case DnssecAlgorithm.RSASHA512:
- GenerateAndAddRsaKey(privateKey.KeyType, privateKey.Algorithm, (privateKey as DnssecRsaPrivateKey).KeySize, privateKey.RolloverDays);
- break;
- case DnssecAlgorithm.ECDSAP256SHA256:
- case DnssecAlgorithm.ECDSAP384SHA384:
- GenerateAndAddEcdsaKey(privateKey.KeyType, privateKey.Algorithm, privateKey.RolloverDays);
- break;
- default:
- throw new NotSupportedException("DNSSEC algorithm is not supported: " + privateKey.Algorithm.ToString());
- }
- PublishAllGeneratedKeys();
- privateKey.SetToRetire();
- }
- public void RetireDnsKey(ushort keyTag)
- {
- if (_dnssecStatus == AuthZoneDnssecStatus.Unsigned)
- throw new DnsServerException("The zone must be signed.");
- DnssecPrivateKey privateKeyToRetire;
- lock (_dnssecPrivateKeys)
- {
- if (!_dnssecPrivateKeys.TryGetValue(keyTag, out privateKeyToRetire))
- throw new DnsServerException("Cannot retire private key: no such private key was found.");
- }
- switch (privateKeyToRetire.KeyType)
- {
- case DnssecPrivateKeyType.KeySigningKey:
- switch (privateKeyToRetire.State)
- {
- case DnssecPrivateKeyState.Ready:
- case DnssecPrivateKeyState.Active:
- if (!RetireKskDnsKeys(new DnssecPrivateKey[] { privateKeyToRetire }, true))
- throw new DnsServerException("Cannot retire private key: no successor key was found to safely retire the key.");
- break;
- default:
- throw new DnsServerException("Cannot retire private key: the KSK private key state must be Ready or Active to be able to retire.");
- }
- break;
- case DnssecPrivateKeyType.ZoneSigningKey:
- switch (privateKeyToRetire.State)
- {
- case DnssecPrivateKeyState.Active:
- if (!RetireZskDnsKeys(new DnssecPrivateKey[] { privateKeyToRetire }, true))
- throw new DnsServerException("Cannot retire private key: no successor key was found to safely retire the key.");
- break;
- default:
- throw new DnsServerException("Cannot retire private key: the ZSK private key state must be Active to be able to retire.");
- }
- break;
- default:
- throw new InvalidOperationException();
- }
- }
- private bool RetireKskDnsKeys(IReadOnlyList<DnssecPrivateKey> kskPrivateKeys, bool ignoreAlgorithm)
- {
- string dnsKeyTags = null;
- foreach (DnssecPrivateKey kskPrivateKey in kskPrivateKeys)
- {
- bool isSafeToRetire = false;
- lock (_dnssecPrivateKeys)
- {
- foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
- {
- DnssecPrivateKey privateKey = privateKeyEntry.Value;
- if ((privateKey.KeyType == DnssecPrivateKeyType.KeySigningKey) && (privateKey.KeyTag != kskPrivateKey.KeyTag) && !privateKey.IsRetiring)
- {
- if (ignoreAlgorithm)
- {
- //manual retire case
- if (privateKey.Algorithm != kskPrivateKey.Algorithm)
- {
- //check if the sucessor ksk has a matching zsk
- bool foundMatchingZsk = false;
- foreach (KeyValuePair<ushort, DnssecPrivateKey> zskPrivateKeyEntry in _dnssecPrivateKeys)
- {
- DnssecPrivateKey zskPrivateKey = zskPrivateKeyEntry.Value;
- if ((zskPrivateKey.KeyType == DnssecPrivateKeyType.ZoneSigningKey) && (zskPrivateKey.Algorithm == privateKey.Algorithm) && (zskPrivateKey.State == DnssecPrivateKeyState.Active) && !zskPrivateKey.IsRetiring)
- {
- foundMatchingZsk = true;
- break;
- }
- }
- if (!foundMatchingZsk)
- continue;
- }
- }
- else
- {
- //rollover case
- if (privateKey.Algorithm != kskPrivateKey.Algorithm)
- continue;
- }
- if (privateKey.State == DnssecPrivateKeyState.Active)
- {
- isSafeToRetire = true;
- break;
- }
- if ((privateKey.State == DnssecPrivateKeyState.Ready) && (kskPrivateKey.State == DnssecPrivateKeyState.Ready))
- {
- isSafeToRetire = true;
- break;
- }
- }
- }
- }
- if (isSafeToRetire)
- {
- kskPrivateKey.SetState(DnssecPrivateKeyState.Retired);
- if (dnsKeyTags is null)
- dnsKeyTags = kskPrivateKey.KeyTag.ToString();
- else
- dnsKeyTags += ", " + kskPrivateKey.KeyTag.ToString();
- }
- }
- if (dnsKeyTags is not null)
- {
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write("The KSK DNSKEYs (" + dnsKeyTags + ") from the primary zone were retired successfully: " + _name);
- return true;
- }
- return false;
- }
- private bool RetireZskDnsKeys(IReadOnlyList<DnssecPrivateKey> zskPrivateKeys, bool ignoreAlgorithm)
- {
- string dnsKeyTags = null;
- List<DnssecPrivateKey> zskToDeactivate = null;
- foreach (DnssecPrivateKey zskPrivateKey in zskPrivateKeys)
- {
- bool isSafeToRetire = false;
- lock (_dnssecPrivateKeys)
- {
- foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
- {
- DnssecPrivateKey privateKey = privateKeyEntry.Value;
- if ((privateKey.KeyType == DnssecPrivateKeyType.ZoneSigningKey) && (privateKey.KeyTag != zskPrivateKey.KeyTag) && (privateKey.State == DnssecPrivateKeyState.Active) && !privateKey.IsRetiring)
- {
- if (ignoreAlgorithm)
- {
- //manual retire case
- if (privateKey.Algorithm != zskPrivateKey.Algorithm)
- {
- //check if the sucessor zsk has a matching ksk
- bool foundMatchingKsk = false;
- foreach (KeyValuePair<ushort, DnssecPrivateKey> kskPrivateKeyEntry in _dnssecPrivateKeys)
- {
- DnssecPrivateKey kskPrivateKey = kskPrivateKeyEntry.Value;
- if ((kskPrivateKey.KeyType == DnssecPrivateKeyType.KeySigningKey) && (kskPrivateKey.Algorithm == privateKey.Algorithm) && ((kskPrivateKey.State == DnssecPrivateKeyState.Ready) || (kskPrivateKey.State == DnssecPrivateKeyState.Active)) && !kskPrivateKey.IsRetiring)
- {
- foundMatchingKsk = true;
- break;
- }
- }
- if (!foundMatchingKsk)
- continue;
- }
- }
- else
- {
- //rollover case
- if (privateKey.Algorithm != zskPrivateKey.Algorithm)
- continue;
- }
- isSafeToRetire = true;
- break;
- }
- }
- }
- if (isSafeToRetire)
- {
- zskPrivateKey.SetState(DnssecPrivateKeyState.Retired);
- if (zskToDeactivate is null)
- zskToDeactivate = new List<DnssecPrivateKey>();
- zskToDeactivate.Add(zskPrivateKey);
- if (dnsKeyTags is null)
- dnsKeyTags = zskPrivateKey.KeyTag.ToString();
- else
- dnsKeyTags += ", " + zskPrivateKey.KeyTag.ToString();
- }
- }
- if (zskToDeactivate is not null)
- DeactivateZskDnsKeys(zskToDeactivate);
- if (dnsKeyTags is not null)
- {
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write("The ZSK DNSKEYs (" + dnsKeyTags + ") from the primary zone were retired successfully: " + _name);
- return true;
- }
- return false;
- }
- private void DeactivateZskDnsKeys(IReadOnlyList<DnssecPrivateKey> zskPrivateKeys)
- {
- //remove all RRSIGs for the DNSKEYs
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- foreach (AuthZone zone in zones)
- {
- IReadOnlyList<DnsResourceRecord> rrsigRecords = zone.GetRecords(DnsResourceRecordType.RRSIG);
- List<DnsResourceRecord> rrsigsToRemove = new List<DnsResourceRecord>();
- foreach (DnsResourceRecord rrsigRecord in rrsigRecords)
- {
- DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
- foreach (DnssecPrivateKey privateKey in zskPrivateKeys)
- {
- if (rrsig.KeyTag == privateKey.KeyTag)
- {
- rrsigsToRemove.Add(rrsigRecord);
- break;
- }
- }
- }
- if (zone.TryDeleteRecords(DnsResourceRecordType.RRSIG, rrsigsToRemove, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords))
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- CommitAndIncrementSerial(deletedRecords);
- TriggerNotify();
- string dnsKeyTags = null;
- foreach (DnssecPrivateKey privateKey in zskPrivateKeys)
- {
- if (dnsKeyTags is null)
- dnsKeyTags = privateKey.KeyTag.ToString();
- else
- dnsKeyTags += ", " + privateKey.KeyTag.ToString();
- }
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write("The ZSK DNSKEYs (" + dnsKeyTags + ") from the primary zone were deactivated successfully: " + _name);
- }
- private void RevokeKskDnsKeys(IReadOnlyList<DnssecPrivateKey> kskPrivateKeys)
- {
- if (!_entries.TryGetValue(DnsResourceRecordType.DNSKEY, out IReadOnlyList<DnsResourceRecord> existingDnsKeyRecords))
- throw new InvalidOperationException();
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> dnsKeyRecords = new List<DnsResourceRecord>();
- foreach (DnsResourceRecord existingDnsKeyRecord in existingDnsKeyRecords)
- {
- bool found = false;
- foreach (DnssecPrivateKey privateKey in kskPrivateKeys)
- {
- if (existingDnsKeyRecord.RDATA.Equals(privateKey.DnsKey))
- {
- found = true;
- break;
- }
- }
- if (!found)
- dnsKeyRecords.Add(existingDnsKeyRecord);
- }
- uint dnsKeyTtl = existingDnsKeyRecords[0].OriginalTtlValue;
- List<ushort> keyTagsToRemove = new List<ushort>(kskPrivateKeys.Count);
- foreach (DnssecPrivateKey privateKey in kskPrivateKeys)
- {
- keyTagsToRemove.Add(privateKey.KeyTag);
- privateKey.SetState(DnssecPrivateKeyState.Revoked);
- DnsResourceRecord revokedDnsKeyRecord = new DnsResourceRecord(_name, DnsResourceRecordType.DNSKEY, DnsClass.IN, dnsKeyTtl, privateKey.DnsKey);
- dnsKeyRecords.Add(revokedDnsKeyRecord);
- }
- if (!TrySetRecords(DnsResourceRecordType.DNSKEY, dnsKeyRecords, out IReadOnlyList<DnsResourceRecord> deletedDnsKeyRecords))
- throw new InvalidOperationException();
- addedRecords.AddRange(dnsKeyRecords);
- deletedRecords.AddRange(deletedDnsKeyRecords);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(dnsKeyRecords);
- if (newRRSigRecords.Count > 0)
- {
- AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- //remove RRSIG for removed keys
- {
- IReadOnlyList<DnsResourceRecord> rrsigRecords = GetRecords(DnsResourceRecordType.RRSIG);
- List<DnsResourceRecord> rrsigsToRemove = new List<DnsResourceRecord>();
- foreach (DnsResourceRecord rrsigRecord in rrsigRecords)
- {
- DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
- if (rrsig.TypeCovered != DnsResourceRecordType.DNSKEY)
- continue;
- foreach (ushort keyTagToRemove in keyTagsToRemove)
- {
- if (rrsig.KeyTag == keyTagToRemove)
- {
- rrsigsToRemove.Add(rrsigRecord);
- break;
- }
- }
- }
- if (TryDeleteRecords(DnsResourceRecordType.RRSIG, rrsigsToRemove, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords))
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- TriggerNotify();
- //update revoked private keys
- string dnsKeyTags = null;
- lock (_dnssecPrivateKeys)
- {
- //remove old entry
- foreach (ushort keyTag in keyTagsToRemove)
- {
- if (_dnssecPrivateKeys.Remove(keyTag))
- {
- if (dnsKeyTags is null)
- dnsKeyTags = keyTag.ToString();
- else
- dnsKeyTags += ", " + keyTag.ToString();
- }
- }
- //add new entry
- foreach (DnssecPrivateKey privateKey in kskPrivateKeys)
- _dnssecPrivateKeys.Add(privateKey.KeyTag, privateKey);
- }
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write("The KSK DNSKEYs (" + dnsKeyTags + ") from the primary zone were revoked successfully: " + _name);
- }
- private void UnpublishDnsKeys(IReadOnlyList<DnssecPrivateKey> deadPrivateKeys)
- {
- if (!_entries.TryGetValue(DnsResourceRecordType.DNSKEY, out IReadOnlyList<DnsResourceRecord> existingDnsKeyRecords))
- throw new InvalidOperationException();
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> dnsKeyRecords = new List<DnsResourceRecord>();
- foreach (DnsResourceRecord existingDnsKeyRecord in existingDnsKeyRecords)
- {
- bool found = false;
- foreach (DnssecPrivateKey privateKey in deadPrivateKeys)
- {
- if (existingDnsKeyRecord.RDATA.Equals(privateKey.DnsKey))
- {
- found = true;
- break;
- }
- }
- if (!found)
- dnsKeyRecords.Add(existingDnsKeyRecord);
- }
- if (dnsKeyRecords.Count < 2)
- throw new InvalidOperationException();
- if (!TrySetRecords(DnsResourceRecordType.DNSKEY, dnsKeyRecords, out IReadOnlyList<DnsResourceRecord> deletedDnsKeyRecords))
- throw new InvalidOperationException();
- addedRecords.AddRange(dnsKeyRecords);
- deletedRecords.AddRange(deletedDnsKeyRecords);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(dnsKeyRecords);
- if (newRRSigRecords.Count > 0)
- {
- AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- //remove RRSig for revoked keys
- {
- IReadOnlyList<DnsResourceRecord> rrsigRecords = GetRecords(DnsResourceRecordType.RRSIG);
- List<DnsResourceRecord> rrsigsToRemove = new List<DnsResourceRecord>();
- foreach (DnsResourceRecord rrsigRecord in rrsigRecords)
- {
- DnsRRSIGRecordData rrsig = rrsigRecord.RDATA as DnsRRSIGRecordData;
- if (rrsig.TypeCovered != DnsResourceRecordType.DNSKEY)
- continue;
- foreach (DnssecPrivateKey privateKey in deadPrivateKeys)
- {
- if (rrsig.KeyTag == privateKey.KeyTag)
- {
- rrsigsToRemove.Add(rrsigRecord);
- break;
- }
- }
- }
- if (TryDeleteRecords(DnsResourceRecordType.RRSIG, rrsigsToRemove, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords))
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- TriggerNotify();
- //remove private keys permanently
- string dnsKeyTags = null;
- lock (_dnssecPrivateKeys)
- {
- foreach (DnssecPrivateKey privateKey in deadPrivateKeys)
- {
- if (_dnssecPrivateKeys.Remove(privateKey.KeyTag))
- {
- if (dnsKeyTags is null)
- dnsKeyTags = privateKey.KeyTag.ToString();
- else
- dnsKeyTags += ", " + privateKey.KeyTag.ToString();
- }
- }
- }
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write("The DNSKEYs (" + dnsKeyTags + ") from the primary zone were unpublished successfully: " + _name);
- }
- private async Task<IReadOnlyList<DnssecPrivateKey>> GetDSPublishedPrivateKeys(IReadOnlyList<DnssecPrivateKey> privateKeys)
- {
- if (_name.Length == 0)
- return privateKeys; //zone is root
- //delete any existing DS entries from cache to allow resolving latest ones
- _dnsServer.CacheZoneManager.DeleteZone(_name);
- IReadOnlyList<DnsDSRecordData> dsRecords = DnsClient.ParseResponseDS(await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(_name, DnsResourceRecordType.DS, DnsClass.IN)));
- List<DnssecPrivateKey> activePrivateKeys = new List<DnssecPrivateKey>(dsRecords.Count);
- foreach (DnsDSRecordData dsRecord in dsRecords)
- {
- foreach (DnssecPrivateKey privateKey in privateKeys)
- {
- if ((dsRecord.KeyTag == privateKey.DnsKey.ComputedKeyTag) && (dsRecord.Algorithm == privateKey.DnsKey.Algorithm) && privateKey.DnsKey.IsDnsKeyValid(_name, dsRecord))
- {
- activePrivateKeys.Add(privateKey);
- break;
- }
- }
- }
- return activePrivateKeys;
- }
- private bool TryRefreshAllSignatures()
- {
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetZoneWithSubDomainZones(_name);
- foreach (AuthZone zone in zones)
- {
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = zone.RefreshSignatures();
- if (newRRSigRecords.Count > 0)
- {
- zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- }
- if ((deletedRecords.Count > 0) || (addedRecords.Count > 0))
- {
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- TriggerNotify();
- return true;
- }
- return false;
- }
- internal override IReadOnlyList<DnsResourceRecord> SignRRSet(IReadOnlyList<DnsResourceRecord> records)
- {
- DnsResourceRecordType rrsetType = records[0].Type;
- List<DnsResourceRecord> rrsigRecords = new List<DnsResourceRecord>();
- uint signatureValidityPeriod = GetSignatureValidityPeriod();
- switch (rrsetType)
- {
- case DnsResourceRecordType.DNSKEY:
- lock (_dnssecPrivateKeys)
- {
- foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
- {
- DnssecPrivateKey privateKey = privateKeyEntry.Value;
- if (privateKey.KeyType != DnssecPrivateKeyType.KeySigningKey)
- continue;
- switch (privateKey.State)
- {
- case DnssecPrivateKeyState.Published:
- case DnssecPrivateKeyState.Ready:
- case DnssecPrivateKeyState.Active:
- case DnssecPrivateKeyState.Revoked:
- rrsigRecords.Add(privateKey.SignRRSet(_name, records, DNSSEC_SIGNATURE_INCEPTION_OFFSET, signatureValidityPeriod));
- break;
- }
- }
- }
- break;
- 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:
- if ((rrsetType == DnsResourceRecordType.NS) && (records[0].Name.Length > _name.Length))
- return Array.Empty<DnsResourceRecord>(); //referrer NS records are not signed
- lock (_dnssecPrivateKeys)
- {
- foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
- {
- DnssecPrivateKey privateKey = privateKeyEntry.Value;
- if (privateKey.KeyType != DnssecPrivateKeyType.ZoneSigningKey)
- continue;
- switch (privateKey.State)
- {
- case DnssecPrivateKeyState.Ready:
- case DnssecPrivateKeyState.Active:
- rrsigRecords.Add(privateKey.SignRRSet(_name, records, DNSSEC_SIGNATURE_INCEPTION_OFFSET, signatureValidityPeriod));
- break;
- }
- }
- }
- break;
- }
- if (rrsigRecords.Count == 0)
- throw new InvalidOperationException("Cannot sign RRSet: no private key was available.");
- return rrsigRecords;
- }
- internal void UpdateDnssecRecordsFor(AuthZone zone, DnsResourceRecordType type)
- {
- //lock to sync this call to prevent inconsistent NSEC/NSEC3 updates
- lock (_dnssecUpdateLock)
- {
- IReadOnlyList<DnsResourceRecord> records = zone.GetRecords(type);
- if (records.Count > 0)
- {
- //rrset added or updated
- //sign rrset
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(records);
- if (newRRSigRecords.Count > 0)
- {
- zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- CommitAndIncrementSerial(deletedRRSigRecords, newRRSigRecords);
- }
- }
- else
- {
- //rrset deleted
- //delete rrsig
- IReadOnlyList<DnsResourceRecord> existingRRSigRecords = zone.GetRecords(DnsResourceRecordType.RRSIG);
- if (existingRRSigRecords.Count > 0)
- {
- List<DnsResourceRecord> recordsToDelete = new List<DnsResourceRecord>();
- foreach (DnsResourceRecord existingRRSigRecord in existingRRSigRecords)
- {
- DnsRRSIGRecordData rrsig = existingRRSigRecord.RDATA as DnsRRSIGRecordData;
- if (rrsig.TypeCovered == type)
- recordsToDelete.Add(existingRRSigRecord);
- }
- if (zone.TryDeleteRecords(DnsResourceRecordType.RRSIG, recordsToDelete, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords))
- CommitAndIncrementSerial(deletedRRSigRecords);
- }
- }
- if (_dnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC)
- {
- UpdateNSecRRSetFor(zone);
- }
- else
- {
- UpdateNSec3RRSetFor(zone);
- int apexLabelCount = DnsRRSIGRecordData.GetLabelCount(_name);
- int zoneLabelCount = DnsRRSIGRecordData.GetLabelCount(zone.Name);
- if ((zoneLabelCount - apexLabelCount) > 1)
- {
- //empty non-terminal (ENT) may exists
- string currentOwnerName = zone.Name;
- while (true)
- {
- currentOwnerName = AuthZoneManager.GetParentZone(currentOwnerName);
- if (currentOwnerName.Equals(_name, StringComparison.OrdinalIgnoreCase))
- break;
- //update NSEC3 rrset for current owner 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
- UpdateNSec3RRSetFor(entZone);
- }
- }
- }
- }
- }
- private void UpdateNSecRRSetFor(AuthZone zone)
- {
- uint ttl = GetZoneSoaMinimum();
- IReadOnlyList<DnsResourceRecord> newNSecRecords = GetUpdatedNSecRRSetFor(zone, ttl);
- if (newNSecRecords.Count > 0)
- {
- DnsResourceRecord newNSecRecord = newNSecRecords[0];
- DnsNSECRecordData newNSec = newNSecRecord.RDATA as DnsNSECRecordData;
- if (newNSec.Types.Count == 2)
- {
- //only NSEC and RRSIG exists so remove NSEC
- IReadOnlyList<DnsResourceRecord> deletedNSecRecords = zone.RemoveNSecRecordsWithRRSig();
- if (deletedNSecRecords.Count > 0)
- CommitAndIncrementSerial(deletedNSecRecords);
- //relink previous nsec
- RelinkPreviousNSecRRSetFor(newNSecRecord, ttl, true);
- }
- else
- {
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- if (!zone.TrySetRecords(DnsResourceRecordType.NSEC, newNSecRecords, out IReadOnlyList<DnsResourceRecord> deletedNSecRecords))
- throw new DnsServerException("Failed to set DNSSEC records. Please try again.");
- addedRecords.AddRange(newNSecRecords);
- deletedRecords.AddRange(deletedNSecRecords);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(newNSecRecords);
- if (newRRSigRecords.Count > 0)
- {
- zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- if (deletedNSecRecords.Count == 0)
- {
- //new NSEC created since no old NSEC was removed
- //relink previous nsec
- RelinkPreviousNSecRRSetFor(newNSecRecord, ttl, false);
- }
- }
- }
- }
- private void UpdateNSec3RRSetFor(AuthZone zone)
- {
- uint ttl = GetZoneSoaMinimum();
- bool noSubDomainExistsForEmptyZone = (zone.IsEmpty || zone.HasOnlyNSec3Records()) && !_dnsServer.AuthZoneManager.SubDomainExists(_name, zone.Name);
- IReadOnlyList<DnsResourceRecord> newNSec3Records = GetUpdatedNSec3RRSetFor(zone, ttl, noSubDomainExistsForEmptyZone);
- if (newNSec3Records.Count > 0)
- {
- DnsResourceRecord newNSec3Record = newNSec3Records[0];
- AuthZone nsec3Zone = _dnsServer.AuthZoneManager.GetOrAddSubDomainZone(_name, newNSec3Record.Name);
- if (nsec3Zone is null)
- throw new InvalidOperationException();
- if (noSubDomainExistsForEmptyZone)
- {
- //no records exists in real zone and no sub domain exists, so remove NSEC3
- IReadOnlyList<DnsResourceRecord> deletedNSec3Records = nsec3Zone.RemoveNSec3RecordsWithRRSig();
- if (deletedNSec3Records.Count > 0)
- CommitAndIncrementSerial(deletedNSec3Records);
- //remove nsec3 sub domain zone if empty since it wont get removed otherwise
- if (nsec3Zone is SubDomainZone nsec3SubDomainZone)
- {
- if (nsec3Zone.IsEmpty)
- _dnsServer.AuthZoneManager.RemoveSubDomainZone(nsec3Zone.Name); //remove empty sub zone
- else
- nsec3SubDomainZone.AutoUpdateState();
- }
- //remove the real zone if empty so that any of the ENT that exists can also be removed later
- if (zone is SubDomainZone subDomainZone)
- {
- if (zone.IsEmpty)
- _dnsServer.AuthZoneManager.RemoveSubDomainZone(zone.Name); //remove empty sub zone
- else
- subDomainZone.AutoUpdateState();
- }
- //relink previous nsec3
- RelinkPreviousNSec3RRSet(newNSec3Record, ttl, true);
- }
- else
- {
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- if (!nsec3Zone.TrySetRecords(DnsResourceRecordType.NSEC3, newNSec3Records, out IReadOnlyList<DnsResourceRecord> deletedNSec3Records))
- throw new DnsServerException("Failed to set DNSSEC records. Please try again.");
- addedRecords.AddRange(newNSec3Records);
- deletedRecords.AddRange(deletedNSec3Records);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(newNSec3Records);
- if (newRRSigRecords.Count > 0)
- {
- nsec3Zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- if (deletedNSec3Records.Count == 0)
- {
- //new NSEC3 created since no old NSEC3 was removed
- //relink previous nsec
- RelinkPreviousNSec3RRSet(newNSec3Record, ttl, false);
- }
- }
- }
- }
- private IReadOnlyList<DnsResourceRecord> GetUpdatedNSecRRSetFor(AuthZone zone, uint ttl)
- {
- AuthZone nextZone = _dnsServer.AuthZoneManager.FindNextSubDomainZone(_name, zone.Name);
- if (nextZone is null)
- nextZone = this;
- return zone.GetUpdatedNSecRRSet(nextZone.Name, ttl);
- }
- private IReadOnlyList<DnsResourceRecord> GetUpdatedNSec3RRSetFor(AuthZone zone, uint ttl, bool forceGetNewRRSet)
- {
- if (!_entries.TryGetValue(DnsResourceRecordType.NSEC3PARAM, out IReadOnlyList<DnsResourceRecord> nsec3ParamRecords))
- throw new InvalidOperationException();
- DnsResourceRecord nsec3ParamRecord = nsec3ParamRecords[0];
- DnsNSEC3PARAMRecordData nsec3Param = nsec3ParamRecord.RDATA as DnsNSEC3PARAMRecordData;
- string hashedOwnerName = nsec3Param.ComputeHashedOwnerNameBase32HexString(zone.Name) + (_name.Length > 0 ? "." + _name : "");
- byte[] nextHashedOwnerName = null;
- //find next hashed owner name
- string currentOwnerName = hashedOwnerName;
- while (true)
- {
- AuthZone nextZone = _dnsServer.AuthZoneManager.FindNextSubDomainZone(_name, currentOwnerName);
- if (nextZone is null)
- break;
- IReadOnlyList<DnsResourceRecord> nextNSec3Records = nextZone.GetRecords(DnsResourceRecordType.NSEC3);
- if (nextNSec3Records.Count > 0)
- {
- nextHashedOwnerName = DnsNSEC3RecordData.GetHashedOwnerNameFrom(nextNSec3Records[0].Name);
- break;
- }
- currentOwnerName = nextZone.Name;
- }
- if (nextHashedOwnerName is null)
- {
- //didnt find next NSEC3 record since current must be last; find the first NSEC3 record
- DnsResourceRecord previousNSec3Record = null;
- while (true)
- {
- AuthZone previousZone = _dnsServer.AuthZoneManager.FindPreviousSubDomainZone(_name, currentOwnerName);
- if (previousZone is null)
- break;
- IReadOnlyList<DnsResourceRecord> previousNSec3Records = previousZone.GetRecords(DnsResourceRecordType.NSEC3);
- if (previousNSec3Records.Count > 0)
- previousNSec3Record = previousNSec3Records[0];
- currentOwnerName = previousZone.Name;
- }
- if (previousNSec3Record is not null)
- nextHashedOwnerName = DnsNSEC3RecordData.GetHashedOwnerNameFrom(previousNSec3Record.Name);
- }
- if (nextHashedOwnerName is null)
- nextHashedOwnerName = DnsNSEC3RecordData.GetHashedOwnerNameFrom(hashedOwnerName); //only 1 NSEC3 record in zone
- IReadOnlyList<DnsResourceRecord> newNSec3Records = zone.CreateNSec3RRSet(hashedOwnerName, nextHashedOwnerName, ttl, nsec3Param.Iterations, nsec3Param.Salt);
- if (forceGetNewRRSet)
- return newNSec3Records;
- AuthZone nsec3Zone = _dnsServer.AuthZoneManager.GetAuthZone(_name, hashedOwnerName);
- if (nsec3Zone is null)
- return newNSec3Records;
- return nsec3Zone.GetUpdatedNSec3RRSet(newNSec3Records);
- }
- private void RelinkPreviousNSecRRSetFor(DnsResourceRecord currentNSecRecord, uint ttl, bool wasRemoved)
- {
- AuthZone previousNsecZone = _dnsServer.AuthZoneManager.FindPreviousSubDomainZone(_name, currentNSecRecord.Name);
- if (previousNsecZone is null)
- return; //current zone is apex
- IReadOnlyList<DnsResourceRecord> newPreviousNSecRecords;
- if (wasRemoved)
- newPreviousNSecRecords = previousNsecZone.GetUpdatedNSecRRSet((currentNSecRecord.RDATA as DnsNSECRecordData).NextDomainName, ttl);
- else
- newPreviousNSecRecords = previousNsecZone.GetUpdatedNSecRRSet(currentNSecRecord.Name, ttl);
- if (newPreviousNSecRecords.Count > 0)
- {
- if (!previousNsecZone.TrySetRecords(DnsResourceRecordType.NSEC, newPreviousNSecRecords, out IReadOnlyList<DnsResourceRecord> deletedNSecRecords))
- throw new DnsServerException("Failed to set DNSSEC records. Please try again.");
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- addedRecords.AddRange(newPreviousNSecRecords);
- deletedRecords.AddRange(deletedNSecRecords);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(newPreviousNSecRecords);
- if (newRRSigRecords.Count > 0)
- {
- previousNsecZone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- }
- }
- private void RelinkPreviousNSec3RRSet(DnsResourceRecord currentNSec3Record, uint ttl, bool wasRemoved)
- {
- DnsNSEC3RecordData currentNSec3 = currentNSec3Record.RDATA as DnsNSEC3RecordData;
- //find the previous NSEC3 and update it
- DnsResourceRecord previousNSec3Record = null;
- AuthZone previousNSec3Zone;
- string currentOwnerName = currentNSec3Record.Name;
- while (true)
- {
- previousNSec3Zone = _dnsServer.AuthZoneManager.FindPreviousSubDomainZone(_name, currentOwnerName);
- if (previousNSec3Zone is null)
- break;
- IReadOnlyList<DnsResourceRecord> previousNSec3Records = previousNSec3Zone.GetRecords(DnsResourceRecordType.NSEC3);
- if (previousNSec3Records.Count > 0)
- {
- previousNSec3Record = previousNSec3Records[0];
- break;
- }
- currentOwnerName = previousNSec3Zone.Name;
- }
- if (previousNSec3Record is null)
- {
- //didnt find previous NSEC3; find the last NSEC3 to update
- if (wasRemoved)
- currentOwnerName = currentNSec3.NextHashedOwnerName + (_name.Length > 0 ? "." + _name : "");
- else
- currentOwnerName = currentNSec3Record.Name;
- while (true)
- {
- AuthZone nextNSec3Zone = _dnsServer.AuthZoneManager.GetAuthZone(_name, currentOwnerName);
- if (nextNSec3Zone is null)
- break;
- IReadOnlyList<DnsResourceRecord> nextNSec3Records = nextNSec3Zone.GetRecords(DnsResourceRecordType.NSEC3);
- if (nextNSec3Records.Count > 0)
- {
- previousNSec3Record = nextNSec3Records[0];
- previousNSec3Zone = nextNSec3Zone;
- string nextHashedOwnerNameString = (previousNSec3Record.RDATA as DnsNSEC3RecordData).NextHashedOwnerName + (_name.Length > 0 ? "." + _name : "");
- if (DnsNSECRecordData.CanonicalComparison(previousNSec3Record.Name, nextHashedOwnerNameString) >= 0)
- break; //found last NSEC3
- //jump to next hashed owner
- currentOwnerName = nextHashedOwnerNameString;
- }
- else
- {
- currentOwnerName = nextNSec3Zone.Name;
- }
- }
- }
- if (previousNSec3Record is null)
- throw new InvalidOperationException();
- DnsNSEC3RecordData previousNSec3 = previousNSec3Record.RDATA as DnsNSEC3RecordData;
- DnsNSEC3RecordData newPreviousNSec3;
- if (wasRemoved)
- newPreviousNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, previousNSec3.Iterations, previousNSec3.Salt, currentNSec3.NextHashedOwnerNameValue, previousNSec3.Types);
- else
- newPreviousNSec3 = new DnsNSEC3RecordData(DnssecNSEC3HashAlgorithm.SHA1, DnssecNSEC3Flags.None, previousNSec3.Iterations, previousNSec3.Salt, DnsNSEC3RecordData.GetHashedOwnerNameFrom(currentNSec3Record.Name), previousNSec3.Types);
- DnsResourceRecord[] newPreviousNSec3Records = new DnsResourceRecord[] { new DnsResourceRecord(previousNSec3Record.Name, DnsResourceRecordType.NSEC3, DnsClass.IN, ttl, newPreviousNSec3) };
- if (!previousNSec3Zone.TrySetRecords(DnsResourceRecordType.NSEC3, newPreviousNSec3Records, out IReadOnlyList<DnsResourceRecord> deletedNSec3Records))
- throw new DnsServerException("Failed to set DNSSEC records. Please try again.");
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- addedRecords.AddRange(newPreviousNSec3Records);
- deletedRecords.AddRange(deletedNSec3Records);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(newPreviousNSec3Records);
- if (newRRSigRecords.Count > 0)
- {
- previousNSec3Zone.AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- }
- private uint GetSignatureValidityPeriod()
- {
- //SOA EXPIRE + 3 days
- return (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).Expire + (3 * 24 * 60 * 60);
- }
- internal DateTime GetDnsKeyStateReadyBy(DnssecPrivateKey privateKey)
- {
- return GetDnsKeyStateReadyOn(privateKey).AddMilliseconds(DNSSEC_TIMER_PERIODIC_INTERVAL);
- }
- private DateTime GetDnsKeyStateReadyOn(DnssecPrivateKey privateKey)
- {
- bool foundOldKsk = false;
- lock (_dnssecPrivateKeys)
- {
- foreach (KeyValuePair<ushort, DnssecPrivateKey> dnssecPrivateKey in _dnssecPrivateKeys)
- {
- DnssecPrivateKey kskPrivateKey = dnssecPrivateKey.Value;
- if (kskPrivateKey.KeyType == DnssecPrivateKeyType.KeySigningKey)
- {
- if ((kskPrivateKey.State == DnssecPrivateKeyState.Ready) || (kskPrivateKey.State == DnssecPrivateKeyState.Active))
- {
- foundOldKsk = true;
- break;
- }
- }
- }
- }
- if (foundOldKsk)
- return privateKey.StateChangedOn.AddSeconds(GetDnsKeyTtl() + GetPropagationDelay());
- else
- return privateKey.StateChangedOn.AddSeconds(GetMaxRecordTtl() + GetPropagationDelay()); //newly signed zone case
- }
- private uint GetPropagationDelay()
- {
- //the max time required to sync zone changes to secondaries if NOTIFY fails to trigger a zone transfer
- DnsSOARecordData soa = _entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData;
- return soa.Refresh + soa.Retry;
- }
- private async Task<uint> GetParentSidePropagationDelayAsync()
- {
- uint parentSidePropagationDelay = 24 * 60 * 60;
- try
- {
- string parent = AuthZoneManager.GetParentZone(_name);
- if (parent is null)
- parent = "";
- DnsDatagram soaResponse = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(parent, DnsResourceRecordType.SOA, DnsClass.IN), 10000);
- if (soaResponse.RCODE == DnsResponseCode.NoError)
- {
- IReadOnlyList<DnsResourceRecord> records;
- if (soaResponse.Answer.Count > 0)
- records = soaResponse.Answer;
- else if (soaResponse.Authority.Count > 0)
- records = soaResponse.Authority;
- else
- records = null;
- if (records is not null)
- {
- foreach (DnsResourceRecord record in records)
- {
- if (record.Type == DnsResourceRecordType.SOA)
- {
- DnsSOARecordData parentSoa = record.RDATA as DnsSOARecordData;
- parentSidePropagationDelay = parentSoa.Refresh + parentSoa.Retry;
- break;
- }
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write(ex);
- }
- return parentSidePropagationDelay;
- }
- private uint GetMaxRecordTtl()
- {
- uint maxTtl = 0;
- foreach (KeyValuePair<DnsResourceRecordType, IReadOnlyList<DnsResourceRecord>> entry in _entries)
- {
- if (entry.Key == DnsResourceRecordType.RRSIG)
- continue;
- IReadOnlyList<DnsResourceRecord> rrset = entry.Value;
- //find min TTL
- uint rrsetTtl = 0;
- foreach (DnsResourceRecord rr in rrset)
- {
- if ((rrsetTtl == 0) || (rrsetTtl > rr.OriginalTtlValue))
- rrsetTtl = rr.OriginalTtlValue;
- }
- if (rrsetTtl > maxTtl)
- maxTtl = rrsetTtl;
- }
- return maxTtl;
- }
- private uint GetMaxRRSigTtl()
- {
- uint maxTtl = 0;
- if (!_entries.TryGetValue(DnsResourceRecordType.RRSIG, out IReadOnlyList<DnsResourceRecord> rrsigRecords))
- throw new InvalidOperationException();
- foreach (DnsResourceRecord rr in rrsigRecords)
- {
- if (rr.OriginalTtlValue > maxTtl)
- maxTtl = rr.OriginalTtlValue;
- }
- return maxTtl;
- }
- private uint GetZoneSoaMinimum()
- {
- return (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).Minimum;
- }
- internal uint GetZoneSoaExpire()
- {
- return (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).Expire;
- }
- private async Task<uint> GetDSTtl()
- {
- uint dsTtl = 24 * 60 * 60;
- try
- {
- DnsDatagram dsResponse = await _dnsServer.DirectQueryAsync(new DnsQuestionRecord(_name, DnsResourceRecordType.DS, DnsClass.IN), 10000);
- if (dsResponse.RCODE == DnsResponseCode.NoError)
- {
- if (dsResponse.Answer.Count > 0)
- {
- //find min TTL
- dsTtl = 0;
- foreach (DnsResourceRecord answer in dsResponse.Answer)
- {
- if (answer.Type == DnsResourceRecordType.DS)
- {
- if ((dsTtl == 0) || (dsTtl > answer.OriginalTtlValue))
- dsTtl = answer.OriginalTtlValue;
- }
- }
- }
- else
- {
- dsTtl = 0; //no DS was found
- }
- }
- }
- catch (Exception ex)
- {
- LogManager log = _dnsServer.LogManager;
- if (log is not null)
- log.Write(ex);
- }
- return dsTtl;
- }
- public uint GetDnsKeyTtl()
- {
- if (_entries.TryGetValue(DnsResourceRecordType.DNSKEY, out IReadOnlyList<DnsResourceRecord> dnsKeyRecords))
- return dnsKeyRecords[0].OriginalTtlValue;
- return 24 * 60 * 60;
- }
- public void UpdateDnsKeyTtl(uint dnsKeyTtl)
- {
- if (_dnssecStatus == AuthZoneDnssecStatus.Unsigned)
- throw new DnsServerException("The zone must be signed.");
- lock (_dnssecPrivateKeys)
- {
- foreach (KeyValuePair<ushort, DnssecPrivateKey> privateKeyEntry in _dnssecPrivateKeys)
- {
- switch (privateKeyEntry.Value.State)
- {
- case DnssecPrivateKeyState.Ready:
- case DnssecPrivateKeyState.Active:
- break;
- default:
- throw new DnsServerException("Cannot update DNSKEY TTL value: one or more private keys have state other than Ready or Active.");
- }
- }
- }
- if (!_entries.TryGetValue(DnsResourceRecordType.DNSKEY, out IReadOnlyList<DnsResourceRecord> dnsKeyRecords))
- throw new InvalidOperationException();
- DnsResourceRecord[] newDnsKeyRecords = new DnsResourceRecord[dnsKeyRecords.Count];
- for (int i = 0; i < dnsKeyRecords.Count; i++)
- {
- DnsResourceRecord dnsKeyRecord = dnsKeyRecords[i];
- newDnsKeyRecords[i] = new DnsResourceRecord(dnsKeyRecord.Name, DnsResourceRecordType.DNSKEY, DnsClass.IN, dnsKeyTtl, dnsKeyRecord.RDATA);
- }
- List<DnsResourceRecord> addedRecords = new List<DnsResourceRecord>();
- List<DnsResourceRecord> deletedRecords = new List<DnsResourceRecord>();
- if (!TrySetRecords(DnsResourceRecordType.DNSKEY, newDnsKeyRecords, out IReadOnlyList<DnsResourceRecord> deletedDnsKeyRecords))
- throw new DnsServerException("Failed to update DNSKEY TTL. Please try again.");
- addedRecords.AddRange(newDnsKeyRecords);
- deletedRecords.AddRange(deletedDnsKeyRecords);
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = SignRRSet(newDnsKeyRecords);
- if (newRRSigRecords.Count > 0)
- {
- AddOrUpdateRRSigRecords(newRRSigRecords, out IReadOnlyList<DnsResourceRecord> deletedRRSigRecords);
- addedRecords.AddRange(newRRSigRecords);
- deletedRecords.AddRange(deletedRRSigRecords);
- }
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- TriggerNotify();
- }
- #endregion
- #region versioning
- internal void CommitAndIncrementSerial(IReadOnlyList<DnsResourceRecord> deletedRecords = null, IReadOnlyList<DnsResourceRecord> addedRecords = null)
- {
- if (_internal)
- return;
- lock (_zoneHistory)
- {
- DnsResourceRecord oldSoaRecord = _entries[DnsResourceRecordType.SOA][0];
- DnsResourceRecord newSoaRecord;
- {
- DnsSOARecordData oldSoa = oldSoaRecord.RDATA as DnsSOARecordData;
- if ((addedRecords is not null) && (addedRecords.Count == 1) && (addedRecords[0].Type == DnsResourceRecordType.SOA))
- {
- DnsResourceRecord addSoaRecord = addedRecords[0];
- DnsSOARecordData addSoa = addSoaRecord.RDATA as DnsSOARecordData;
- uint serial = GetNewSerial(oldSoa.Serial, addSoa.Serial, addSoaRecord.GetAuthRecordInfo().UseSoaSerialDateScheme);
- newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, addSoaRecord.TTL, new DnsSOARecordData(addSoa.PrimaryNameServer, addSoa.ResponsiblePerson, serial, addSoa.Refresh, addSoa.Retry, addSoa.Expire, addSoa.Minimum)) { Tag = addSoaRecord.Tag };
- addedRecords = null;
- }
- else
- {
- uint serial = GetNewSerial(oldSoa.Serial, 0, oldSoaRecord.GetAuthRecordInfo().UseSoaSerialDateScheme);
- newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, oldSoaRecord.TTL, new DnsSOARecordData(oldSoa.PrimaryNameServer, oldSoa.ResponsiblePerson, serial, oldSoa.Refresh, oldSoa.Retry, oldSoa.Expire, oldSoa.Minimum)) { Tag = oldSoaRecord.Tag };
- }
- }
- DnsResourceRecord[] newSoaRecords = new DnsResourceRecord[] { newSoaRecord };
- //update SOA
- _entries[DnsResourceRecordType.SOA] = newSoaRecords;
- IReadOnlyList<DnsResourceRecord> newRRSigRecords = null;
- IReadOnlyList<DnsResourceRecord> deletedRRSigRecords = null;
- if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
- {
- //sign SOA and update RRSig
- newRRSigRecords = SignRRSet(newSoaRecords);
- AddOrUpdateRRSigRecords(newRRSigRecords, out deletedRRSigRecords);
- }
- //remove RR info from old SOA to allow creating new RR info for it during SetDeletedOn()
- oldSoaRecord.Tag = null;
- //start commit
- oldSoaRecord.GetAuthRecordInfo().DeletedOn = DateTime.UtcNow;
- //write removed
- _zoneHistory.Add(oldSoaRecord);
- if (deletedRecords is not null)
- {
- foreach (DnsResourceRecord deletedRecord in deletedRecords)
- {
- if (deletedRecord.GetAuthRecordInfo().Disabled)
- continue;
- _zoneHistory.Add(deletedRecord);
- if (deletedRecord.Type == DnsResourceRecordType.NS)
- {
- IReadOnlyList<DnsResourceRecord> glueRecords = deletedRecord.GetAuthRecordInfo().GlueRecords;
- if (glueRecords is not null)
- _zoneHistory.AddRange(glueRecords);
- }
- }
- }
- if (deletedRRSigRecords is not null)
- _zoneHistory.AddRange(deletedRRSigRecords);
- //write added
- _zoneHistory.Add(newSoaRecord);
- if (addedRecords is not null)
- {
- foreach (DnsResourceRecord addedRecord in addedRecords)
- {
- if (addedRecord.GetAuthRecordInfo().Disabled)
- continue;
- _zoneHistory.Add(addedRecord);
- if (addedRecord.Type == DnsResourceRecordType.NS)
- {
- IReadOnlyList<DnsResourceRecord> glueRecords = addedRecord.GetAuthRecordInfo().GlueRecords;
- if (glueRecords is not null)
- _zoneHistory.AddRange(glueRecords);
- }
- }
- }
- if (newRRSigRecords is not null)
- _zoneHistory.AddRange(newRRSigRecords);
- //end commit
- CleanupHistory(_zoneHistory);
- }
- }
- private static uint GetNewSerial(uint oldSerial, uint updateSerial, bool useSoaSerialDateScheme)
- {
- if (useSoaSerialDateScheme)
- {
- string strOldSerial = oldSerial.ToString();
- string strOldSerialDate = null;
- byte counter = 0;
- if (strOldSerial.Length == 10)
- {
- //parse old serial
- strOldSerialDate = strOldSerial.Substring(0, 8);
- counter = byte.Parse(strOldSerial.Substring(8));
- }
- string strSerialDate = DateTime.UtcNow.ToString("yyyyMMdd");
- if (strOldSerialDate is null)
- {
- //transitioning to date scheme
- return uint.Parse(strSerialDate + counter.ToString().PadLeft(2, '0'));
- }
- else if (strSerialDate.Equals(strOldSerialDate))
- {
- //same date
- if (counter < 99)
- {
- counter++;
- return uint.Parse(strSerialDate + counter.ToString().PadLeft(2, '0'));
- }
- else
- {
- //more than 100 increments
- return uint.Parse(strSerialDate + counter.ToString().PadLeft(2, '0')) + 1;
- }
- }
- else if (uint.Parse(strSerialDate) > uint.Parse(strOldSerialDate))
- {
- //later date
- return uint.Parse(strSerialDate + "00");
- }
- }
- //default
- uint serial = oldSerial;
- if (updateSerial > serial)
- serial = updateSerial;
- else if (serial < uint.MaxValue)
- serial++;
- else
- serial = 1;
- return serial;
- }
- #endregion
- #region public
- 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.");
- default:
- foreach (DnsResourceRecord record in records)
- {
- if (record.GetAuthRecordInfo().Disabled)
- throw new DnsServerException("Cannot set records: disabling records in a signed zones is not supported.");
- }
- break;
- }
- }
- switch (type)
- {
- case DnsResourceRecordType.CNAME:
- case DnsResourceRecordType.DS:
- throw new InvalidOperationException("Cannot set " + type.ToString() + " record at zone apex.");
- case DnsResourceRecordType.SOA:
- if ((records.Count != 1) || !records[0].Name.Equals(_name, StringComparison.OrdinalIgnoreCase))
- throw new InvalidOperationException("Invalid SOA record.");
- DnsResourceRecord newSoaRecord = records[0];
- DnsSOARecordData newSoa = newSoaRecord.RDATA as DnsSOARecordData;
- if (newSoaRecord.OriginalTtlValue > newSoa.Expire)
- throw new DnsServerException("Failed to set records: TTL cannot be greater than SOA EXPIRE.");
- if (newSoa.Retry > newSoa.Refresh)
- throw new DnsServerException("Failed to set records: SOA RETRY cannot be greater than SOA REFRESH.");
- if (newSoa.Refresh > newSoa.Expire)
- throw new DnsServerException("Failed to set records: SOA REFRESH cannot be greater than SOA EXPIRE.");
- //remove any record info except serial date scheme and comments
- bool useSoaSerialDateScheme;
- string comments;
- {
- AuthRecordInfo recordInfo = newSoaRecord.GetAuthRecordInfo();
- useSoaSerialDateScheme = recordInfo.UseSoaSerialDateScheme;
- comments = recordInfo.Comments;
- }
- newSoaRecord.Tag = null; //remove old record info
- {
- AuthRecordInfo recordInfo = newSoaRecord.GetAuthRecordInfo();
- recordInfo.UseSoaSerialDateScheme = useSoaSerialDateScheme;
- recordInfo.Comments = comments;
- }
- uint oldSoaMinimum = GetZoneSoaMinimum();
- //setting new SOA
- CommitAndIncrementSerial(null, records);
- if (oldSoaMinimum != newSoa.Minimum)
- {
- switch (_dnssecStatus)
- {
- case AuthZoneDnssecStatus.SignedWithNSEC:
- RefreshNSec();
- break;
- case AuthZoneDnssecStatus.SignedWithNSEC3:
- RefreshNSec3();
- break;
- }
- }
- TriggerNotify();
- break;
- case DnsResourceRecordType.DNSKEY:
- case DnsResourceRecordType.RRSIG:
- case DnsResourceRecordType.NSEC:
- case DnsResourceRecordType.NSEC3PARAM:
- 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.");
- if (!TrySetRecords(type, records, out IReadOnlyList<DnsResourceRecord> deletedRecords))
- throw new DnsServerException("Failed to set records. Please try again.");
- CommitAndIncrementSerial(deletedRecords, records);
- if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
- UpdateDnssecRecordsFor(this, type);
- TriggerNotify();
- break;
- }
- }
- 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.");
- default:
- if (record.GetAuthRecordInfo().Disabled)
- throw new DnsServerException("Cannot add record: disabling records in a signed zones is not supported.");
- break;
- }
- }
- switch (record.Type)
- {
- case DnsResourceRecordType.APP:
- throw new InvalidOperationException("Cannot add record: use SetRecords() for " + record.Type.ToString() + " record");
- case DnsResourceRecordType.DS:
- throw new InvalidOperationException("Cannot set DS record at zone apex.");
- case DnsResourceRecordType.DNSKEY:
- case DnsResourceRecordType.RRSIG:
- case DnsResourceRecordType.NSEC:
- case DnsResourceRecordType.NSEC3PARAM:
- 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.");
- AddRecord(record, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords);
- if (addedRecords.Count > 0)
- {
- CommitAndIncrementSerial(deletedRecords, addedRecords);
- if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
- UpdateDnssecRecordsFor(this, record.Type);
- TriggerNotify();
- }
- break;
- }
- }
- public override bool DeleteRecords(DnsResourceRecordType type)
- {
- switch (type)
- {
- case DnsResourceRecordType.SOA:
- throw new InvalidOperationException("Cannot delete SOA record.");
- case DnsResourceRecordType.DNSKEY:
- case DnsResourceRecordType.RRSIG:
- case DnsResourceRecordType.NSEC:
- case DnsResourceRecordType.NSEC3PARAM:
- case DnsResourceRecordType.NSEC3:
- throw new InvalidOperationException("Cannot delete DNSSEC records.");
- default:
- if (_entries.TryRemove(type, out IReadOnlyList<DnsResourceRecord> removedRecords))
- {
- CommitAndIncrementSerial(removedRecords);
- if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
- UpdateDnssecRecordsFor(this, type);
- TriggerNotify();
- return true;
- }
- return false;
- }
- }
- public override bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData record)
- {
- switch (type)
- {
- case DnsResourceRecordType.SOA:
- throw new InvalidOperationException("Cannot delete SOA record.");
- case DnsResourceRecordType.DNSKEY:
- case DnsResourceRecordType.RRSIG:
- case DnsResourceRecordType.NSEC:
- case DnsResourceRecordType.NSEC3PARAM:
- case DnsResourceRecordType.NSEC3:
- throw new InvalidOperationException("Cannot delete DNSSEC records.");
- default:
- if (TryDeleteRecord(type, record, out DnsResourceRecord deletedRecord))
- {
- CommitAndIncrementSerial(new DnsResourceRecord[] { deletedRecord });
- if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
- UpdateDnssecRecordsFor(this, type);
- TriggerNotify();
- return true;
- }
- return false;
- }
- }
- public override void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
- {
- switch (oldRecord.Type)
- {
- case DnsResourceRecordType.SOA:
- throw new InvalidOperationException("Cannot update record: use SetRecords() for " + oldRecord.Type.ToString() + " record");
- case DnsResourceRecordType.DNSKEY:
- case DnsResourceRecordType.RRSIG:
- case DnsResourceRecordType.NSEC:
- case DnsResourceRecordType.NSEC3PARAM:
- case DnsResourceRecordType.NSEC3:
- throw new InvalidOperationException("Cannot update DNSSEC records.");
- default:
- if (oldRecord.Type != newRecord.Type)
- throw new InvalidOperationException("Old and new record types do not match.");
- if ((_dnssecStatus != AuthZoneDnssecStatus.Unsigned) && newRecord.GetAuthRecordInfo().Disabled)
- throw new DnsServerException("Cannot update record: disabling records in a signed zones is not supported.");
- if (newRecord.OriginalTtlValue > GetZoneSoaExpire())
- throw new DnsServerException("Cannot update record: TTL cannot be greater than SOA EXPIRE.");
- if (!TryDeleteRecord(oldRecord.Type, oldRecord.RDATA, out DnsResourceRecord deletedRecord))
- throw new InvalidOperationException("Cannot update record: the record does not exists to be updated.");
- AddRecord(newRecord, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords);
- List<DnsResourceRecord> allDeletedRecords = new List<DnsResourceRecord>(deletedRecords.Count + 1);
- allDeletedRecords.Add(deletedRecord);
- allDeletedRecords.AddRange(deletedRecords);
- CommitAndIncrementSerial(allDeletedRecords, addedRecords);
- if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)
- UpdateDnssecRecordsFor(this, oldRecord.Type);
- TriggerNotify();
- break;
- }
- }
- #endregion
- #region properties
- public bool Internal
- { get { return _internal; } }
- public override bool Disabled
- {
- get { return _disabled; }
- set
- {
- if (_disabled != value)
- {
- _disabled = value;
- if (_disabled)
- DisableNotifyTimer();
- else
- TriggerNotify();
- }
- }
- }
- public override AuthZoneTransfer ZoneTransfer
- {
- get { return _zoneTransfer; }
- set
- {
- if (_internal)
- throw new InvalidOperationException();
- base.ZoneTransfer = value;
- }
- }
- public override AuthZoneNotify Notify
- {
- get { return _notify; }
- set
- {
- if (_internal)
- throw new InvalidOperationException();
- base.Notify = value;
- }
- }
- public override AuthZoneUpdate Update
- {
- get { return _update; }
- set
- {
- if (_internal)
- throw new InvalidOperationException();
- base.Update = value;
- }
- }
- public IReadOnlyCollection<DnssecPrivateKey> DnssecPrivateKeys
- {
- get
- {
- if (_dnssecPrivateKeys is null)
- return null;
- lock (_dnssecPrivateKeys)
- {
- return _dnssecPrivateKeys.Values;
- }
- }
- }
- #endregion
- }
- }
|