diff --git a/APIDOCS.md b/APIDOCS.md index 058f75112..54383cbf3 100644 --- a/APIDOCS.md +++ b/APIDOCS.md @@ -2673,13 +2673,13 @@ WHERE: - `token`: The session token generated by the `login` or the `createToken` call. - `node` (optional): The node domain name for which the this API call is intended. When unspecified, the current node is used. This parameter can be used only when Clustering is initialized. - `zone`: The name of the primary zone to sign. -- `algorithm`: The algorithm to be used for signing. Valid values are [`RSA`, `ECDSA`, `EDDSA`]. +- `algorithm`: The algorithm to be used for signing. Valid values are [`RSA`, `ECDSA`, `EDDSA`, `MLDSA`]. - `pemKskPrivateKey` (optional): The user specified private key in PEM format for Key Signing Key (KSK). When this parameter is specified, the private key specified is used instead of automatically generating it. - `pemZskPrivateKey` (optional): The user specified private key in PEM format for Zone Signing Key (ZSK). When this parameter is specified, the private key specified is used instead of automatically generating it. - `hashAlgorithm` (optional): The hash algorithm to be used when using `RSA` algorithm. Valid values are [`MD5`, `SHA1`, `SHA256`, `SHA512`]. This optional parameter is required when using `RSA` algorithm. - `kskKeySize` (optional): The size of the Key Signing Key (KSK) in bits to be used when using `RSA` algorithm. This optional parameter is required when using `RSA` algorithm. - `zskKeySize` (optional): The size of the Zone Signing Key (ZSK) in bits to be used when using `RSA` algorithm. This optional parameter is required when using `RSA` algorithm. -- `curve` (optional): The name of the curve to be used when using `ECDSA` or `EDDSA` algorithm. Valid values are [`P256`, `P384`] for `ECDSA` algorithm and [`ED25519`, `ED448`] for `EDDSA` algorithm. This optional parameter is required when using `ECDSA` or `EDDSA` algorithm. +- `curve` (optional): The name of the curve to be used when using `ECDSA` or `EDDSA` algorithm. Valid values are [`P256`, `P384`] for `ECDSA` algorithm and [`ED25519`, `ED448`] for `EDDSA` algorithm. This optional parameter is required when using `ECDSA` or `EDDSA` algorithm. The `MLDSA` algorithm does not use this parameter; it always generates an ML-DSA-44 key. - `dnsKeyTtl` (optional): The TTL value to be used for DNSKEY records. Default value is `86400` when not specified. - `zskRolloverDays` (optional): The frequency in days that the DNS server must automatically rollover the Zone Signing Keys (ZSK) in the zone. Valid range is 0-365 days where 0 disables rollover. Default value is `30` when not specified. - `nxProof` (optional): The type of proof of non-existence that must be used for signing the zone. Valid values are [`NSEC`, `NSEC3`]. Default value is `NSEC` when not specified. @@ -2975,11 +2975,11 @@ WHERE: - `zone`: The name of the primary zone. - `keyType`: The type of key for which the private key is to be generated. Valid values are [`KeySigningKey`, `ZoneSigningKey`]. - `rolloverDays` (optional): The frequency in days that the DNS server must automatically rollover the private key in the zone. Valid range is 0-365 days where 0 disables rollover. Default value is 90 days for Zone Signing Key (ZSK) and 0 days for Key Signing Key (KSK). -- `algorithm`: The algorithm to be used for signing. Valid values are [`RSA`, `ECDSA`, `EDDSA`]. +- `algorithm`: The algorithm to be used for signing. Valid values are [`RSA`, `ECDSA`, `EDDSA`, `MLDSA`]. - `pemPrivateKey` (optional): Specifies a user generated private key in PEM format to add. When not specified a private key will be automatically generated. - `hashAlgorithm` (optional): The hash algorithm to be used when using `RSA` algorithm. Valid values are [`MD5`, `SHA1`, `SHA256`, `SHA512`]. This optional parameter is required when using `RSA` algorithm. - `keySize` (optional): The size of the generated private key in bits to be used when using `RSA` algorithm. This optional parameter is required when using `RSA` algorithm. -- `curve` (optional): The name of the curve to be used when using `ECDSA` or `EDDSA` algorithm. Valid values are [`P256`, `P384`] for `ECDSA` algorithm and [`ED25519`, `ED448`] for `EDDSA` algorithm. This optional parameter is required when using `ECDSA` or `EDDSA` algorithm. +- `curve` (optional): The name of the curve to be used when using `ECDSA` or `EDDSA` algorithm. Valid values are [`P256`, `P384`] for `ECDSA` algorithm and [`ED25519`, `ED448`] for `EDDSA` algorithm. This optional parameter is required when using `ECDSA` or `EDDSA` algorithm. The `MLDSA` algorithm does not use this parameter; it always generates an ML-DSA-44 key. RESPONSE: ``` @@ -3193,7 +3193,7 @@ WHERE: - `naptrReplacement` (optional): This parameter is required for adding the `NAPTR` record. - `dname` (optional): The DNAME domain name. This option is required for adding `DNAME` record. - `keyTag` (optional): This parameter is required for adding `DS` record. -- `algorithm` (optional): Valid values are [`RSAMD5`, `DSA`, `RSASHA1`, `DSA-NSEC3-SHA1`, `RSASHA1-NSEC3-SHA1`, `RSASHA256`, `RSASHA512`, `ECC-GOST`, `ECDSAP256SHA256`, `ECDSAP384SHA384`, `ED25519`, `ED448`]. This parameter is required for adding `DS` record. +- `algorithm` (optional): Valid values are [`RSAMD5`, `DSA`, `RSASHA1`, `DSA-NSEC3-SHA1`, `RSASHA1-NSEC3-SHA1`, `RSASHA256`, `RSASHA512`, `ECC-GOST`, `ECDSAP256SHA256`, `ECDSAP384SHA384`, `ED25519`, `ED448`, `MLDSA44`]. This parameter is required for adding `DS` record. - `digestType` (optional): Valid values are [`SHA1`, `SHA256`, `GOST-R-34-11-94`, `SHA384`]. This parameter is required for adding `DS` record. - `digest` (optional): A hex string value. This parameter is required for adding `DS` record. - `sshfpAlgorithm` (optional): Valid values are [`RSA`, `DSA`, `ECDSA`, `Ed25519`, `Ed448`]. This parameter is required for adding `SSHFP` record. diff --git a/DnsServerCore/Dns/Dnssec/DnssecMldsaPrivateKey.cs b/DnsServerCore/Dns/Dnssec/DnssecMldsaPrivateKey.cs new file mode 100644 index 000000000..03015dbda --- /dev/null +++ b/DnsServerCore/Dns/Dnssec/DnssecMldsaPrivateKey.cs @@ -0,0 +1,96 @@ +/* +Technitium DNS Server +Copyright (C) 2026 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 . + +*/ + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using System.IO; +using TechnitiumLibrary.IO; +using TechnitiumLibrary.Net.Dns.Dnssec; +using TechnitiumLibrary.Net.Dns.ResourceRecords; + +namespace DnsServerCore.Dns.Dnssec +{ + class DnssecMldsaPrivateKey : DnssecPrivateKey + { + #region variables + + MLDsaPrivateKeyParameters _privateKey; + + #endregion + + #region constructor + + public DnssecMldsaPrivateKey(DnssecPrivateKeyType keyType, MLDsaPrivateKeyParameters privateKey) + : base(DnssecAlgorithm.MLDSA44, keyType) + { + _privateKey = privateKey; + + InitDnsKey(); + } + + public DnssecMldsaPrivateKey(DnssecAlgorithm algorithm, BinaryReader bR, int version) + : base(algorithm, bR, version) + { + InitDnsKey(); + } + + #endregion + + #region private + + private void InitDnsKey() + { + InitDnsKey(new DnssecMldsaPublicKey(_privateKey.GetPublicKeyEncoded())); + } + + #endregion + + #region protected + + protected override byte[] SignHash(byte[] hash) + { + //pure ML-DSA-44 signing with an empty context; must match the verifier's signer construction + MLDsaSigner signer = new MLDsaSigner(MLDsaParameters.ml_dsa_44, false); + signer.Init(true, _privateKey); + signer.BlockUpdate(hash, 0, hash.Length); + + return signer.GenerateSignature(); + } + + protected override void ReadPrivateKeyFrom(BinaryReader bR) + { + switch (Algorithm) + { + case DnssecAlgorithm.MLDSA44: + _privateKey = MLDsaPrivateKeyParameters.FromSeed(MLDsaParameters.ml_dsa_44, bR.ReadBuffer()); + break; + + default: + throw new InvalidDataException(); + } + } + + protected override void WritePrivateKeyTo(BinaryWriter bW) + { + bW.WriteBuffer(_privateKey.GetSeed()); + } + + #endregion + } +} diff --git a/DnsServerCore/Dns/Dnssec/DnssecPrivateKey.cs b/DnsServerCore/Dns/Dnssec/DnssecPrivateKey.cs index e3a891604..82286b8c2 100644 --- a/DnsServerCore/Dns/Dnssec/DnssecPrivateKey.cs +++ b/DnsServerCore/Dns/Dnssec/DnssecPrivateKey.cs @@ -211,6 +211,9 @@ public static DnssecPrivateKey Create(DnssecAlgorithm algorithm, DnssecPrivateKe case DnssecAlgorithm.ED448: return new DnssecEddsaPrivateKey(keyType, new Ed448PrivateKeyParameters(RandomNumberGenerator.GetBytes(57))); + case DnssecAlgorithm.MLDSA44: + return new DnssecMldsaPrivateKey(keyType, MLDsaPrivateKeyParameters.FromSeed(MLDsaParameters.ml_dsa_44, RandomNumberGenerator.GetBytes(32))); + default: throw new NotSupportedException("DNSSEC algorithm is not supported: " + algorithm.ToString()); } @@ -275,6 +278,18 @@ public static DnssecPrivateKey Create(DnssecAlgorithm algorithm, DnssecPrivateKe return new DnssecEddsaPrivateKey(keyType, privateKey); } + case DnssecAlgorithm.MLDSA44: + using (PemReader pemReader = new PemReader(new StringReader(pemPrivateKey))) + { + if ((pemReader.ReadObject() is not MLDsaPrivateKeyParameters privateKey) || (privateKey.Parameters != MLDsaParameters.ml_dsa_44)) + throw new ArgumentException($"The ML-DSA ({(keyType == DnssecPrivateKeyType.KeySigningKey ? "KSK" : "ZSK")}) private key must be for ML-DSA-44.", nameof(pemPrivateKey)); + + if (privateKey.GetSeed() is null) + throw new ArgumentException($"The ML-DSA-44 ({(keyType == DnssecPrivateKeyType.KeySigningKey ? "KSK" : "ZSK")}) private key must use the seed encoding.", nameof(pemPrivateKey)); + + return new DnssecMldsaPrivateKey(keyType, privateKey); + } + default: throw new NotSupportedException("DNSSEC algorithm is not supported: " + algorithm.ToString()); } @@ -308,6 +323,9 @@ public static DnssecPrivateKey ReadFrom(BinaryReader bR) case DnssecAlgorithm.ED448: return new DnssecEddsaPrivateKey(algorithm, bR, version); + case DnssecAlgorithm.MLDSA44: + return new DnssecMldsaPrivateKey(algorithm, bR, version); + default: throw new NotSupportedException("DNSSEC algorithm is not supported: " + algorithm.ToString()); } diff --git a/DnsServerCore/Dns/Zones/PrimaryZone.cs b/DnsServerCore/Dns/Zones/PrimaryZone.cs index dfbc4485b..d182ce4a6 100644 --- a/DnsServerCore/Dns/Zones/PrimaryZone.cs +++ b/DnsServerCore/Dns/Zones/PrimaryZone.cs @@ -1280,6 +1280,7 @@ public void RolloverDnsKey(ushort keyTag) case DnssecAlgorithm.ECDSAP384SHA384: case DnssecAlgorithm.ED25519: case DnssecAlgorithm.ED448: + case DnssecAlgorithm.MLDSA44: GenerateAndAddPrivateKey(privateKey.KeyType, privateKey.Algorithm, privateKey.RolloverDays); break; diff --git a/DnsServerCore/WebServiceZonesApi.cs b/DnsServerCore/WebServiceZonesApi.cs index 6f31ae7ae..c99d47181 100644 --- a/DnsServerCore/WebServiceZonesApi.cs +++ b/DnsServerCore/WebServiceZonesApi.cs @@ -2285,6 +2285,22 @@ public void SignPrimaryZone(HttpContext context) } break; + case "MLDSA": + { + DnssecAlgorithm dnssecAlgorithm = DnssecAlgorithm.MLDSA44; + + if (pemKskPrivateKey is null) + kskPrivateKey = DnssecPrivateKey.Create(dnssecAlgorithm, DnssecPrivateKeyType.KeySigningKey); + else + kskPrivateKey = DnssecPrivateKey.Create(dnssecAlgorithm, DnssecPrivateKeyType.KeySigningKey, pemKskPrivateKey); + + if (pemZskPrivateKey is null) + zskPrivateKey = DnssecPrivateKey.Create(dnssecAlgorithm, DnssecPrivateKeyType.ZoneSigningKey); + else + zskPrivateKey = DnssecPrivateKey.Create(dnssecAlgorithm, DnssecPrivateKeyType.ZoneSigningKey, pemZskPrivateKey); + } + break; + default: throw new NotSupportedException("Algorithm is not supported: " + algorithm); } @@ -2718,6 +2734,24 @@ public void AddPrimaryZoneDnssecPrivateKey(HttpContext context) } break; + case "MLDSA": + { + DnssecAlgorithm dnssecAlgorithm = DnssecAlgorithm.MLDSA44; + + if (pemPrivateKey is null) + { + privateKey = _dnsWebService._dnsServer.AuthZoneManager.GenerateAndAddPrimaryZoneDnssecPrivateKey(zoneName, keyType, dnssecAlgorithm, rolloverDays); + } + else + { + privateKey = DnssecPrivateKey.Create(dnssecAlgorithm, keyType, pemPrivateKey); + privateKey.RolloverDays = rolloverDays; + + _dnsWebService._dnsServer.AuthZoneManager.AddPrimaryZoneDnssecPrivateKey(zoneName, privateKey); + } + } + break; + default: throw new NotSupportedException("Algorithm is not supported: " + algorithm); } diff --git a/DnsServerCore/www/index.html b/DnsServerCore/www/index.html index d6b819de5..946d6d6ef 100644 --- a/DnsServerCore/www/index.html +++ b/DnsServerCore/www/index.html @@ -4539,6 +4539,7 @@ + @@ -5540,6 +5541,12 @@