123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042 |
- /*
- 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 = oldSoa.Serial;
- if (addSoa.Serial > serial)
- serial = addSoa.Serial;
- else if (serial < uint.MaxValue)
- serial++;
- else
- serial = 1;
- 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 = oldSoa.Serial;
- if (serial < uint.MaxValue)
- serial++;
- else
- serial = 1;
- 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);
- }
- }
- #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 comments
- string comments = newSoaRecord.GetAuthRecordInfo().Comments;
- newSoaRecord.Tag = null; //remove old record info
- newSoaRecord.GetAuthRecordInfo().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
- }
- }
|