DnssecPrivateKey.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using System;
  16. using System.Collections.Generic;
  17. using System.IO;
  18. using System.Security.Cryptography;
  19. using System.Text;
  20. using TechnitiumLibrary.Net.Dns.Dnssec;
  21. using TechnitiumLibrary.Net.Dns.EDnsOptions;
  22. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  23. namespace DnsServerCore.Dns.Dnssec
  24. {
  25. //DNSSEC Key Rollover Timing Considerations
  26. //https://datatracker.ietf.org/doc/html/rfc7583
  27. public enum DnssecPrivateKeyType : byte
  28. {
  29. Unknown = 0,
  30. KeySigningKey = 1,
  31. ZoneSigningKey = 2
  32. }
  33. public enum DnssecPrivateKeyState : byte
  34. {
  35. Unknown = 0,
  36. /// <summary>
  37. /// Although keys may be created immediately prior to first
  38. /// use, some implementations may find it convenient to
  39. /// create a pool of keys in one operation and draw from it
  40. /// as required. (Note: such a pre-generated pool must be
  41. /// secured against surreptitious use.) In the timelines
  42. /// below, before the first event, the keys are considered to
  43. /// be created but not yet used: they are said to be in the
  44. /// "Generated" state.
  45. /// </summary>
  46. Generated = 1,
  47. /// <summary>
  48. /// A key enters the published state when either it or its associated data
  49. /// first appears in the appropriate zone.
  50. /// </summary>
  51. Published = 2,
  52. /// <summary>
  53. /// The DNSKEY or its associated data have been published for long enough
  54. /// to guarantee that copies of the key(s) it is replacing (or associated
  55. /// data related to that key) have expired from caches.
  56. /// </summary>
  57. Ready = 3,
  58. /// <summary>
  59. /// The data is starting to be used for validation. In the
  60. /// case of a ZSK, it means that the key is now being used to
  61. /// sign RRsets and that both it and the created RRSIGs
  62. /// appear in the zone. In the case of a KSK, it means that
  63. /// it is possible to use it to validate a DNSKEY RRset as
  64. /// both the DNSKEY and DS records are present in their
  65. /// respective zones. Note that when this state is entered,
  66. /// it may not be possible for validating resolvers to use
  67. /// the data for validation in all cases: the zone signing
  68. /// may not have finished or the data might not have reached
  69. /// the resolver because of propagation delays and/or caching
  70. /// issues. If this is the case, the resolver will have to
  71. /// rely on the predecessor data instead.
  72. /// </summary>
  73. Active = 4,
  74. /// <summary>
  75. /// The data has ceased to be used for validation. In the
  76. /// case of a ZSK, it means that the key is no longer used to
  77. /// sign RRsets. In the case of a KSK, it means that the
  78. /// successor DNSKEY and DS records are in place. In both
  79. /// cases, the key (and its associated data) can be removed
  80. /// as soon as it is safe to do so, i.e., when all validating
  81. /// resolvers are able to use the new key and associated data
  82. /// to validate the zone.However, until this happens, the
  83. /// current key and associated data must remain in their
  84. /// respective zones.
  85. /// </summary>
  86. Retired = 5,
  87. /// <summary>
  88. /// The key and its associated data are present in their
  89. /// respective zones, but there is no longer information
  90. /// anywhere that requires their presence for use in
  91. /// validation. Hence, they can be removed at any time.
  92. /// </summary>
  93. Dead = 6,
  94. /// <summary>
  95. /// Both the DNSKEY and its associated data have been removed
  96. /// from their respective zones.
  97. /// </summary>
  98. Removed = 7,
  99. /// <summary>
  100. /// The DNSKEY is published for a period with the "revoke"
  101. /// bit set as a way of notifying validating resolvers that
  102. /// have configured it as a trust anchor, as used in
  103. /// [RFC5011], that it is about to be removed from the zone.
  104. /// This state is used when [RFC5011] considerations are in
  105. /// effect (see Section 3.3.4).
  106. /// </summary>
  107. Revoked = 8
  108. }
  109. public abstract class DnssecPrivateKey
  110. {
  111. #region variables
  112. readonly DnssecAlgorithm _algorithm;
  113. readonly DnssecPrivateKeyType _keyType;
  114. DnssecPrivateKeyState _state;
  115. DateTime _stateChangedOn;
  116. bool _isRetiring;
  117. ushort _rolloverDays;
  118. DnsDNSKEYRecordData _dnsKey;
  119. #endregion
  120. #region constructor
  121. protected DnssecPrivateKey(DnssecAlgorithm algorithm, DnssecPrivateKeyType keyType)
  122. {
  123. _algorithm = algorithm;
  124. _keyType = keyType;
  125. _state = DnssecPrivateKeyState.Generated;
  126. _stateChangedOn = DateTime.UtcNow;
  127. }
  128. protected DnssecPrivateKey(DnssecAlgorithm algorithm, BinaryReader bR)
  129. {
  130. _algorithm = algorithm;
  131. _keyType = (DnssecPrivateKeyType)bR.ReadByte();
  132. _state = (DnssecPrivateKeyState)bR.ReadByte();
  133. _stateChangedOn = DateTime.UnixEpoch.AddSeconds(bR.ReadInt64());
  134. _isRetiring = bR.ReadBoolean();
  135. _rolloverDays = bR.ReadUInt16();
  136. ReadPrivateKeyFrom(bR);
  137. }
  138. #endregion
  139. #region static
  140. public static DnssecPrivateKey Create(DnssecAlgorithm algorithm, DnssecPrivateKeyType keyType, int keySize = -1)
  141. {
  142. switch (algorithm)
  143. {
  144. case DnssecAlgorithm.RSAMD5:
  145. case DnssecAlgorithm.RSASHA1:
  146. case DnssecAlgorithm.RSASHA1_NSEC3_SHA1:
  147. case DnssecAlgorithm.RSASHA256:
  148. case DnssecAlgorithm.RSASHA512:
  149. if ((keySize < 1024) || (keySize > 4096))
  150. throw new ArgumentOutOfRangeException(nameof(keySize), "Valid RSA key size range is between 1024-4096 bits.");
  151. using (RSA rsa = RSA.Create(keySize))
  152. {
  153. return new DnssecRsaPrivateKey(algorithm, keyType, keySize, rsa.ExportParameters(true));
  154. }
  155. case DnssecAlgorithm.ECDSAP256SHA256:
  156. using (ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256))
  157. {
  158. return new DnssecEcdsaPrivateKey(algorithm, keyType, ecdsa.ExportParameters(true));
  159. }
  160. case DnssecAlgorithm.ECDSAP384SHA384:
  161. using (ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP384))
  162. {
  163. return new DnssecEcdsaPrivateKey(algorithm, keyType, ecdsa.ExportParameters(true));
  164. }
  165. default:
  166. throw new NotSupportedException("DNSSEC algorithm is not supported: " + algorithm.ToString());
  167. }
  168. }
  169. public static DnssecPrivateKey ReadFrom(BinaryReader bR)
  170. {
  171. if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DK")
  172. throw new InvalidDataException("DNSSEC private key format is invalid.");
  173. int version = bR.ReadByte();
  174. switch (version)
  175. {
  176. case 1:
  177. DnssecAlgorithm algorithm = (DnssecAlgorithm)bR.ReadByte();
  178. switch (algorithm)
  179. {
  180. case DnssecAlgorithm.RSAMD5:
  181. case DnssecAlgorithm.RSASHA1:
  182. case DnssecAlgorithm.RSASHA1_NSEC3_SHA1:
  183. case DnssecAlgorithm.RSASHA256:
  184. case DnssecAlgorithm.RSASHA512:
  185. return new DnssecRsaPrivateKey(algorithm, bR);
  186. case DnssecAlgorithm.ECDSAP256SHA256:
  187. case DnssecAlgorithm.ECDSAP384SHA384:
  188. return new DnssecEcdsaPrivateKey(algorithm, bR);
  189. default:
  190. throw new NotSupportedException("DNSSEC algorithm is not supported: " + algorithm.ToString());
  191. }
  192. default:
  193. throw new InvalidDataException("DNSSEC private key version not supported: " + version);
  194. }
  195. }
  196. #endregion
  197. #region protected
  198. protected void InitDnsKey(DnssecPublicKey publicKey)
  199. {
  200. DnsDnsKeyFlag flags = DnsDnsKeyFlag.ZoneKey;
  201. if (KeyType == DnssecPrivateKeyType.KeySigningKey)
  202. flags |= DnsDnsKeyFlag.SecureEntryPoint;
  203. if (_state == DnssecPrivateKeyState.Revoked)
  204. flags |= DnsDnsKeyFlag.Revoke;
  205. _dnsKey = new DnsDNSKEYRecordData(flags, 3, _algorithm, publicKey);
  206. }
  207. protected abstract byte[] SignHash(byte[] hash);
  208. protected abstract void ReadPrivateKeyFrom(BinaryReader bR);
  209. protected abstract void WritePrivateKeyTo(BinaryWriter bW);
  210. #endregion
  211. #region internal
  212. internal DnsResourceRecord SignRRSet(string signersName, IReadOnlyList<DnsResourceRecord> records, uint signatureInceptionOffset, uint signatureValidityPeriod)
  213. {
  214. DnsResourceRecord firstRecord = records[0];
  215. DnsRRSIGRecordData unsignedRRSigRecord = new DnsRRSIGRecordData(firstRecord.Type, _algorithm, DnsRRSIGRecordData.GetLabelCount(firstRecord.Name), firstRecord.OriginalTtlValue, Convert.ToUInt32((DateTime.UtcNow.AddSeconds(signatureValidityPeriod) - DateTime.UnixEpoch).TotalSeconds % uint.MaxValue), Convert.ToUInt32((DateTime.UtcNow.AddSeconds(-signatureInceptionOffset) - DateTime.UnixEpoch).TotalSeconds % uint.MaxValue), DnsKey.ComputedKeyTag, signersName, null);
  216. if (!DnsRRSIGRecordData.TryGetRRSetHash(unsignedRRSigRecord, records, out byte[] hash, out EDnsExtendedDnsErrorCode extendedDnsErrorCode))
  217. throw new DnsServerException("Failed to sign record set: " + extendedDnsErrorCode.ToString());
  218. byte[] signature = SignHash(hash);
  219. DnsRRSIGRecordData signedRRSigRecord = new DnsRRSIGRecordData(unsignedRRSigRecord.TypeCovered, unsignedRRSigRecord.Algorithm, unsignedRRSigRecord.Labels, unsignedRRSigRecord.OriginalTtl, unsignedRRSigRecord.SignatureExpiration, unsignedRRSigRecord.SignatureInception, unsignedRRSigRecord.KeyTag, unsignedRRSigRecord.SignersName, signature);
  220. return new DnsResourceRecord(firstRecord.Name, DnsResourceRecordType.RRSIG, firstRecord.Class, firstRecord.OriginalTtlValue, signedRRSigRecord);
  221. }
  222. internal void SetState(DnssecPrivateKeyState state)
  223. {
  224. if (_state >= state)
  225. return; //ignore; state cannot be updated to lower value
  226. _state = state;
  227. _stateChangedOn = DateTime.UtcNow;
  228. if (_state == DnssecPrivateKeyState.Revoked)
  229. InitDnsKey(_dnsKey.PublicKey);
  230. }
  231. internal void SetToRetire()
  232. {
  233. _isRetiring = true;
  234. }
  235. internal bool IsRolloverNeeded()
  236. {
  237. return (_rolloverDays > 0) && (DateTime.UtcNow > _stateChangedOn.AddDays(_rolloverDays));
  238. }
  239. internal void WriteTo(BinaryWriter bW)
  240. {
  241. bW.Write(Encoding.ASCII.GetBytes("DK")); //format
  242. bW.Write((byte)1); //version
  243. bW.Write((byte)_algorithm);
  244. bW.Write((byte)_keyType);
  245. bW.Write((byte)_state);
  246. bW.Write(Convert.ToInt64((_stateChangedOn - DateTime.UnixEpoch).TotalSeconds));
  247. bW.Write(_isRetiring);
  248. bW.Write(_rolloverDays);
  249. WritePrivateKeyTo(bW);
  250. }
  251. #endregion
  252. #region properties
  253. public DnssecAlgorithm Algorithm
  254. { get { return _algorithm; } }
  255. public DnssecPrivateKeyType KeyType
  256. { get { return _keyType; } }
  257. public DnssecPrivateKeyState State
  258. { get { return _state; } }
  259. public DateTime StateChangedOn
  260. { get { return _stateChangedOn; } }
  261. public bool IsRetiring
  262. { get { return _isRetiring; } }
  263. public ushort RolloverDays
  264. {
  265. get { return _rolloverDays; }
  266. set
  267. {
  268. if (_keyType == DnssecPrivateKeyType.ZoneSigningKey)
  269. {
  270. if (value > 365)
  271. throw new ArgumentOutOfRangeException(nameof(RolloverDays), "Zone Signing Key (ZSK) automatic rollover days valid range is 0-365.");
  272. switch (_state)
  273. {
  274. case DnssecPrivateKeyState.Generated:
  275. case DnssecPrivateKeyState.Published:
  276. case DnssecPrivateKeyState.Ready:
  277. case DnssecPrivateKeyState.Active:
  278. if (_isRetiring)
  279. throw new InvalidOperationException("Zone Signing Key (ZSK) automatic rollover cannot be set since it is set to retire.");
  280. break;
  281. default:
  282. throw new InvalidOperationException("Zone Signing Key (ZSK) automatic rollover cannot be set due to invalid key state.");
  283. }
  284. }
  285. else
  286. {
  287. if (value != 0)
  288. throw new NotSupportedException("Automatic rollover is not supported for Key Signing Keys (KSK).");
  289. }
  290. _rolloverDays = value;
  291. }
  292. }
  293. public DnsDNSKEYRecordData DnsKey
  294. { get { return _dnsKey; } }
  295. public ushort KeyTag
  296. { get { return _dnsKey.ComputedKeyTag; } }
  297. #endregion
  298. }
  299. }