{"record":{"id":"d5bc31af0ff4d0e1","repo":"TechnitiumSoftware/DnsServer","slug":"the-private-key-must-be-a-zone-signing-key","errorCode":null,"errorMessage":"The private key must be a Zone Signing Key.","messagePattern":"The private key must be a Zone Signing Key\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/PrimaryZone.cs","lineNumber":451,"sourceCode":"            {\n                Timer dnssecTimer = _dnssecTimer;\n                if (dnssecTimer is not null)\n                {\n                    lock (dnssecTimer)\n                    {\n                        dnssecTimer.Change(DNSSEC_TIMER_PERIODIC_INTERVAL, Timeout.Infinite);\n                    }\n                }\n            }\n        }\n\n        public void SignZone(DnssecPrivateKey kskPrivateKey, DnssecPrivateKey zskPrivateKey, uint dnsKeyTtl, bool useNSec3, ushort iterations = 0, byte saltLength = 0)\n        {\n            if (kskPrivateKey.KeyType != DnssecPrivateKeyType.KeySigningKey)\n                throw new ArgumentException(\"The private key must be a Key Signing Key.\", nameof(kskPrivateKey));\n\n            if (zskPrivateKey.KeyType != DnssecPrivateKeyType.ZoneSigningKey)\n                throw new ArgumentException(\"The private key must be a Zone Signing Key.\", nameof(zskPrivateKey));\n\n            byte[] salt = null;\n\n            if (useNSec3)\n            {\n                if (saltLength > 32)\n                    throw new ArgumentOutOfRangeException(nameof(saltLength), \"NSEC3 salt length valid range is 0-32\");\n\n                if (saltLength > 0)\n                {\n                    salt = new byte[saltLength];\n                    RandomNumberGenerator.Fill(salt);\n                }\n                else\n                {\n                    salt = [];\n                }\n            }","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/PrimaryZone.cs#L433-L469","documentation":"Thrown by PrimaryZone.SignZone(kskPrivateKey, zskPrivateKey, ...) when zskPrivateKey.KeyType != DnssecPrivateKeyType.ZoneSigningKey. The ZSK signs the non-DNSKEY RRsets, so a KSK in the second slot is rejected with ArgumentException naming 'zskPrivateKey' before salt/iteration handling.","triggerScenarios":"primaryZone.SignZone(ksk, wrongKey, ...) where the second argument's KeyType != ZoneSigningKey.","commonSituations":"Reusing a single KSK for both slots; mislabeled keys imported from PEM/KSK store.","solutions":["Pass a key whose KeyType == DnssecPrivateKeyType.ZoneSigningKey as the second argument.","Generate and persist a dedicated ZSK alongside the KSK.","Assert the ZSK role at load time."],"exampleFix":"// before\nzone.SignZone(ksk, ksk2, ttl, false);\n\n// after\nvar zsk = keys.Single(k => k.KeyType == DnssecPrivateKeyType.ZoneSigningKey);\nzone.SignZone(ksk, zsk, ttl, false);","handlingStrategy":"validation","validationCode":"if (zskPrivateKey.KeyType != DnssecPrivateKeyType.ZoneSigningKey)\n    throw new ArgumentException(\"A ZoneSigningKey is required.\");\nzone.SignZone(kskPrivateKey, zskPrivateKey, ttl, useNSec3);","typeGuard":"static bool IsZsk(DnssecPrivateKey k) => k.KeyType == DnssecPrivateKeyType.ZoneSigningKey;","tryCatchPattern":null,"preventionTips":["Always provision a dedicated ZSK alongside the KSK.","Assert the ZSK role before signing.","Use the (ksk, zsk) overload to make roles explicit."],"tags":["csharp","dns","dnssec","validation","technitium-dns"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}