Skip to content

Commit a9afe32

Browse files
committed
Add comprehensive DNS TXT record RFC compliance validation tests
1 parent 382925e commit a9afe32

File tree

1 file changed

+143
-2
lines changed

1 file changed

+143
-2
lines changed

internal/verification/token_test.go

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const (
1313
errMsgGenToken = "GenerateVerificationToken() error = %v"
1414
errMsgGenTokenNormal = "GenerateVerificationToken() should succeed in normal conditions: %v"
1515
errMsgGenTokenWithInfo = "GenerateTokenWithInfo() error = %v"
16+
dnsRecordPrefix = "mcp-verify="
1617
)
1718

1819
func TestGenerateVerificationToken(t *testing.T) {
@@ -228,7 +229,7 @@ func TestGenerateTokenWithInfo(t *testing.T) {
228229
t.Errorf("TokenInfo.Encoding = %s, want base64url", tokenInfo.Encoding)
229230
}
230231

231-
expectedDNSRecord := "mcp-verify=" + tokenInfo.Token
232+
expectedDNSRecord := dnsRecordPrefix + tokenInfo.Token
232233
if tokenInfo.DNSRecord != expectedDNSRecord {
233234
t.Errorf("TokenInfo.DNSRecord = %s, want %s", tokenInfo.DNSRecord, expectedDNSRecord)
234235
}
@@ -283,14 +284,154 @@ func TestTokenDNSSafety(t *testing.T) {
283284
}
284285

285286
// Test full DNS record format
286-
dnsRecord := "mcp-verify=" + token
287+
dnsRecord := dnsRecordPrefix + token
287288
MaxDNSRecordLength := 255
288289
if len(dnsRecord) > MaxDNSRecordLength {
289290
t.Errorf("DNS record too long (%d chars): %s", len(dnsRecord), dnsRecord)
290291
}
291292
}
292293
}
293294

295+
func TestDNSTXTRecordRFCCompliance(t *testing.T) {
296+
// Test DNS TXT record format compliance according to RFC 1035 and RFC 1464
297+
token, err := verification.GenerateVerificationToken()
298+
if err != nil {
299+
t.Fatalf(errMsgGenToken, err)
300+
}
301+
302+
dnsRecord := dnsRecordPrefix + token
303+
304+
// RFC 1035: DNS names and TXT records have specific length limitations
305+
// TXT record data must not exceed 255 octets per string
306+
if len(dnsRecord) > 255 {
307+
t.Errorf("DNS TXT record exceeds 255 character limit: %d chars", len(dnsRecord))
308+
}
309+
310+
// RFC 1464: TXT records should follow attribute=value format
311+
if !strings.Contains(dnsRecord, "=") {
312+
t.Error("DNS TXT record missing required '=' separator")
313+
}
314+
315+
parts := strings.SplitN(dnsRecord, "=", 2)
316+
if len(parts) != 2 {
317+
t.Error("DNS TXT record should have exactly one '=' separator")
318+
}
319+
320+
attribute := parts[0]
321+
value := parts[1]
322+
323+
// Validate attribute name (should be "mcp-verify")
324+
expectedAttribute := strings.TrimSuffix(dnsRecordPrefix, "=")
325+
if attribute != expectedAttribute {
326+
t.Errorf("DNS TXT record attribute = %s, want %s", attribute, expectedAttribute)
327+
}
328+
329+
// Validate that value is our token
330+
if value != token {
331+
t.Errorf("DNS TXT record value = %s, want %s", value, token)
332+
}
333+
334+
// Test that the record contains only ASCII printable characters (RFC compliant)
335+
for i, char := range dnsRecord {
336+
if char < 32 || char > 126 {
337+
t.Errorf("DNS TXT record contains non-ASCII printable character at position %d: %c (code %d)", i, char, char)
338+
}
339+
}
340+
}
341+
342+
func TestDNSTXTRecordTokenValidation(t *testing.T) {
343+
// Test that tokens in DNS records are valid according to our format
344+
for i := 0; i < 50; i++ {
345+
token, err := verification.GenerateVerificationToken()
346+
if err != nil {
347+
t.Fatalf(errMsgGenTokenIteration, err, i)
348+
}
349+
350+
dnsRecord := dnsRecordPrefix + token
351+
352+
// Extract token from DNS record
353+
if !strings.HasPrefix(dnsRecord, dnsRecordPrefix) {
354+
t.Errorf("DNS record missing expected prefix: %s", dnsRecord)
355+
continue
356+
}
357+
358+
extractedToken := strings.TrimPrefix(dnsRecord, dnsRecordPrefix)
359+
360+
// Validate extracted token format
361+
if !verification.ValidateTokenFormat(extractedToken) {
362+
t.Errorf("Extracted token from DNS record failed validation: %s", extractedToken)
363+
}
364+
365+
// Ensure token matches what we generated
366+
if extractedToken != token {
367+
t.Errorf("Extracted token %s does not match generated token %s", extractedToken, token)
368+
}
369+
}
370+
}
371+
372+
func TestDNSTXTRecordSpecialCharacters(t *testing.T) {
373+
// Test that DNS records handle RFC-compliant special characters correctly
374+
token, err := verification.GenerateVerificationToken()
375+
if err != nil {
376+
t.Fatalf(errMsgGenToken, err)
377+
}
378+
379+
dnsRecord := dnsRecordPrefix + token
380+
381+
// Characters that should NOT appear in our DNS records
382+
prohibitedChars := []rune{
383+
0, // NULL
384+
9, // TAB
385+
10, // LF
386+
13, // CR
387+
34, // Double quote
388+
92, // Backslash
389+
127, // DEL
390+
}
391+
392+
for _, prohibited := range prohibitedChars {
393+
if strings.ContainsRune(dnsRecord, prohibited) {
394+
t.Errorf("DNS record contains prohibited character: %c (code %d)", prohibited, prohibited)
395+
}
396+
}
397+
398+
// Characters that SHOULD be allowed (base64url safe)
399+
allowedChars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="
400+
for _, char := range dnsRecord {
401+
if !strings.ContainsRune(allowedChars, char) {
402+
t.Errorf("DNS record contains unexpected character: %c (code %d)", char, char)
403+
}
404+
}
405+
}
406+
407+
func TestDNSTXTRecordLength(t *testing.T) {
408+
// Test DNS TXT record length constraints
409+
token, err := verification.GenerateVerificationToken()
410+
if err != nil {
411+
t.Fatalf(errMsgGenToken, err)
412+
}
413+
414+
dnsRecord := dnsRecordPrefix + token
415+
416+
// RFC 1035: TXT record strings are limited to 255 octets
417+
maxTXTRecordLength := 255
418+
if len(dnsRecord) > maxTXTRecordLength {
419+
t.Errorf("DNS TXT record length %d exceeds RFC limit of %d", len(dnsRecord), maxTXTRecordLength)
420+
}
421+
422+
// Calculate expected length: "mcp-verify=" (11 chars) + token (22 chars) = 33 chars
423+
expectedLength := 11 + 22 // len("mcp-verify=") + token length
424+
if len(dnsRecord) != expectedLength {
425+
t.Errorf("DNS TXT record length %d, expected %d", len(dnsRecord), expectedLength)
426+
}
427+
428+
// Ensure we have reasonable margin below the limit
429+
marginRequired := 50 // Leave room for future changes
430+
if len(dnsRecord) > (maxTXTRecordLength - marginRequired) {
431+
t.Errorf("DNS TXT record length %d too close to limit, needs %d char margin", len(dnsRecord), marginRequired)
432+
}
433+
}
434+
294435
// Benchmark tests for performance
295436
func BenchmarkGenerateVerificationToken(b *testing.B) {
296437
for i := 0; i < b.N; i++ {

0 commit comments

Comments
 (0)