{"record":{"id":"85ce87c3f5d4e54c","repo":"TechnitiumSoftware/DnsServer","slug":"the-private-key-must-be-a-key-signing-key","errorCode":null,"errorMessage":"The private key must be a Key Signing Key.","messagePattern":"The private key must be a Key Signing Key\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/PrimaryZone.cs","lineNumber":448,"sourceCode":"                _dnsServer.LogManager.Write(ex);\n            }\n            finally\n            {\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                {","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/PrimaryZone.cs#L430-L466","documentation":"Thrown by PrimaryZone.SignZone(kskPrivateKey, zskPrivateKey, ...) when the kskPrivateKey's KeyType is not DnssecPrivateKeyType.KeySigningKey. A KSK signs the DNSKEY RRset (and the DS chain), so passing a ZSK here would break the trust anchor. The guard raises ArgumentException with the 'kskPrivateKey' paramName before any signing work.","triggerScenarios":"primaryZone.SignZone(aZsk, anotherZsk, ...) where the first argument's KeyType != KeySigningKey.","commonSituations":"Swapping the two key arguments; generating two ZSKs by mistake; loading keys from storage without checking their declared role.","solutions":["Pass a key whose KeyType == DnssecPrivateKeyType.KeySigningKey as the first argument.","When generating keys, create exactly one KSK and one ZSK and label them.","Validate both keys' roles before calling SignZone."],"exampleFix":"// before\nzone.SignZone(zsk1, zsk2, ttl, false);\n\n// after\nvar ksk = keys.Single(k => k.KeyType == DnssecPrivateKeyType.KeySigningKey);\nvar zsk = keys.Single(k => k.KeyType == DnssecPrivateKeyType.ZoneSigningKey);\nzone.SignZone(ksk, zsk, ttl, false);","handlingStrategy":"validation","validationCode":"if (kskPrivateKey.KeyType != DnssecPrivateKeyType.KeySigningKey)\n    throw new ArgumentException(\"A KeySigningKey is required.\");\nzone.SignZone(kskPrivateKey, zskPrivateKey, ttl, useNSec3);","typeGuard":"static bool IsKsk(DnssecPrivateKey k) => k.KeyType == DnssecPrivateKeyType.KeySigningKey;","tryCatchPattern":null,"preventionTips":["Generate and label exactly one KSK and one ZSK.","Validate the KSK role at key-load time.","Pass keys by role, never by position alone."],"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"}