Skip to content

Normalize trailing dot when matching hostnames against certificate pins - #9734

Open
adityaanikam wants to merge 2 commits into
lysine-dev:mainfrom
adityaanikam:fix-certpinner-trailing-dot-9724
Open

adityaanikam wants to merge 2 commits into
lysine-dev:mainfrom
adityaanikam:fix-certpinner-trailing-dot-9724

Conversation

@adityaanikam

@adityaanikam adityaanikam commented Aug 28, 2026 •

Copy link
Copy Markdown

Fixes #9724.

Credit to Joshua Rogers (MegaManSec) for finding and reporting this, with a clear write-up and working PoC in the issue.

CertificatePinner.Pin.matchesHostname() compares hostname against pattern with plain string equality (or region matching for wildcard patterns), with no normalization of either side. hostname comes from address.url.host in ConnectPlan, which explicitly preserves a trailing dot for an absolute DNS name. A pin's pattern is developer-authored config and never has one. So a pin for example.com provides no protection at all against a connection to example.com. -- same host at the DNS level, but the pin is silently skipped, and the connection is verified against an ordinary CA-issued certificate instead.

OkHostnameVerifier already treats both spellings as equivalent for exactly this reason (it normalizes both hostname and pattern to absolute form before comparing). CertificatePinner never got the same treatment.

Fix: strip a single trailing dot from hostname at the top of matchesHostname, before the wildcard/exact-match dispatch. All three branches (**., *., and exact) already reference the same hostname identifier, so this one change covers them uniformly. pattern is left untouched -- it is never legitimately going to carry a trailing dot in practice, and the reported issue is specifically about the hostname side.

Added testMatchesHostnameWithTrailingDot to CertificatePinnerKotlinTest, covering both the exact-match and wildcard branches directly on Pin.matchesHostname, plus the reporter's own findMatchingPins() shape.

Verified with a decisive negative control: reverting only the source fix (keeping the new test) reproduces the exact reported behavior -- the test fails with "expected: but was: " on the trailing-dot assertion, confirming the old code silently skips the pin for the dotted hostname.

@yschimke

Copy link
Copy Markdown
Collaborator

Run './gradlew spotlessApply' to fix all violations.

@yschimke yschimke left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matches the logic tested here

  @Test
  fun trailingDotIsOkay() {
    val name251 = "a.".repeat(125) + "a"
    assertThat(parse("http://a./").toString()).isEqualTo("http://a./")
    assertThat(parse("http://${name251}a./").toString()).isEqualTo("http://${name251}a./")
    assertThat(parse("http://${name251}aa/").toString()).isEqualTo("http://${name251}aa/")
    assertInvalid("http://${name251}aa./", "Invalid URL host: \"${name251}aa.\"")
  }
Copilot AI balanced review requested due to automatic review settings October 18, 2026 20:50

This comment was marked as spam.

@adityaanikam

Copy link
Copy Markdown
Author

Ran spotlessApply. It reformatted one line in CertificatePinnerKotlinTest.

@JakeWharton

Copy link
Copy Markdown
Collaborator

LLM use is not allowed on our repos. Please do not trigger it again.

This branch has not been deployed

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

Labels

None yet

4 participants