package rfc2136 import ( "encoding/base64" "fmt" "strings" "github.com/miekg/dns" ) // tsigKey is a single pre-shared TSIG credential. The plugin holds one // per declared `tsig-key` directive, keyed in RFC2136.TSIGKeys by the // canonical (lowercased, trailing-dot) key name. // // Phase 1.2 stores the algorithm and decoded secret. Phase 1.4 will // use them for actual signature verification via dns.TsigSecret + // dns.Msg.IsTsig() / dns.TsigVerify(). type tsigKey struct { // Algorithm is the canonical miekg/dns algorithm identifier, e.g. // dns.HmacSHA256 ("hmac-sha256."). Stored in dns-library form so // it's directly usable when wiring TSIG verification later. Algorithm string // Secret is the base64-decoded raw bytes of the shared secret. // Decoding at parse time means a malformed secret fails Corefile // load (loud), not mid-query (silent). Secret []byte } // supportedTSIGAlgorithms maps user-facing algorithm names (as written // in Corefile) to the canonical names recognised by miekg/dns. Only // SHA-family entries are included — HMAC-MD5 is deprecated and not // supported here on principle. var supportedTSIGAlgorithms = map[string]string{ "hmac-sha1": dns.HmacSHA1, "hmac-sha224": dns.HmacSHA224, "hmac-sha256": dns.HmacSHA256, "hmac-sha384": dns.HmacSHA384, "hmac-sha512": dns.HmacSHA512, } // parseTSIGAlgorithm validates and canonicalises the algorithm name // from a `tsig-key` directive. Returns the miekg/dns canonical form // (which has a trailing dot per DNS-name convention). func parseTSIGAlgorithm(s string) (string, error) { canon, ok := supportedTSIGAlgorithms[strings.ToLower(s)] if !ok { return "", fmt.Errorf("unsupported TSIG algorithm %q (supported: hmac-sha1, hmac-sha224, hmac-sha256, hmac-sha384, hmac-sha512)", s) } return canon, nil } // decodeTSIGSecret validates the base64-encoded TSIG secret from the // Corefile and returns the raw bytes. Rejects empty secrets and any // secret shorter than 8 bytes (well under the HMAC algorithm's block // size for any supported algorithm — anything that short is almost // certainly a typo, not deliberate). func decodeTSIGSecret(s string) ([]byte, error) { raw, err := base64.StdEncoding.DecodeString(s) if err != nil { return nil, fmt.Errorf("invalid base64 in TSIG secret: %w", err) } if len(raw) < 8 { return nil, fmt.Errorf("TSIG secret too short (%d bytes after base64-decode; need >= 8)", len(raw)) } return raw, nil } // canonicalKeyName normalises a TSIG key name to lowercase + trailing // dot. miekg/dns expects the dot-terminated FQDN form internally, so // we coerce here once at parse-time rather than every lookup. func canonicalKeyName(name string) string { name = strings.ToLower(name) if !strings.HasSuffix(name, ".") { name += "." } return name }