Skip to content

Fix domain name encoding to Latin1 and canonical case folding to ASCII-only - #63

Open
Hemsby wants to merge 1 commit into
TechnitiumSoftware:masterfrom
Hemsby:feature/dnssec-white-lies-latin1-encoding
Open

Fix domain name encoding to Latin1 and canonical case folding to ASCII-only#63
Hemsby wants to merge 1 commit into
TechnitiumSoftware:masterfrom
Hemsby:feature/dnssec-white-lies-latin1-encoding

Conversation

@Hemsby

@Hemsby Hemsby commented Aug 20, 2026

Copy link
Copy Markdown

Bug

SerializeDomainName/DeserializeDomainName use Encoding.ASCII, which
silently corrupts any label byte >= 0x80 to ? on write and throws on
read. DNS wire-format labels are octet strings (RFC 1035), not restricted
to ASCII, so a well-formed name containing such a byte is currently
mishandled rather than round-tripped faithfully.

Fix

Encoding.Latin1 maps codepoints/bytes 0-255 one to one, a strict
superset of ASCII, so this is a no-op for every name that worked before
and only changes behavior for the previously-broken case.

A related, currently-latent bug this would otherwise expose

RFC 4034 section 6.2 canonical form requires lowercasing ASCII A-Z only,
but CanonicallySerializedResourceRecord.Create and
DnsNSECRecordData.CanonicalComparison both used
string.ToLowerInvariant(), which performs full Unicode case folding and
would change the byte value of Latin-1 supplement codepoints (e.g. 0xC0 to
0xE0). That corrupts the bytes that feed into RRSIG generation and NSEC
canonical ordering for such names. This has been dormant because those
bytes could never survive the ASCII round-trip to reach this code - fixing
the encoding without also fixing this would have introduced a live
signature-correctness bug for real inbound names.

Added DnsDatagram.ToLowerInvariantAscii, which folds only A-Z and leaves
every other byte untouched, and used it at both call sites.

Testing

Verified against RFC 4470's own worked example and several edge cases via
direct testing of the serialize/deserialize round trip and canonical
comparison for names containing bytes outside the ASCII range.

…I-only

SerializeDomainName/DeserializeDomainName used Encoding.ASCII, which
silently corrupts any label byte >= 0x80 to '?' on write and throws on
read. DNS wire-format labels are octet strings (RFC 1035), not restricted
to ASCII, so a well-formed name containing such a byte was previously
mishandled. Encoding.Latin1 maps codepoints/bytes 0-255 one to one, a
strict superset of ASCII, so this is a no-op for every name that worked
before and only changes behavior for the previously-broken case.

Also fixes a related, currently-latent correctness bug this would have
newly exposed: RFC 4034 section 6.2 canonical form requires lowercasing
ASCII A-Z only, but CanonicallySerializedResourceRecord.Create and
DnsNSECRecordData.CanonicalComparison both used
string.ToLowerInvariant(), which performs full Unicode case folding and
would change the byte value of Latin-1 supplement codepoints (e.g.
0xC0 to 0xE0), corrupting the bytes that feed into RRSIG generation and
NSEC canonical ordering for such names. Added
DnsDatagram.ToLowerInvariantAscii, which folds only A-Z and leaves every
other byte untouched, and used it at both call sites.
@ShreyasZare

Copy link
Copy Markdown
Member

Thanks for the PR. This fix looks good at a glance. Will study it soon and get it merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants