mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 00:26:29 +00:00
fix(tls): also cover derived OAuth subdomain in served cert SAN list
#337's first commit added the OAuth-derivation to the DNS interceptor but missed the served TLS certificate. With a serverURL of `http://mac.fritz.box:8000` the cert SAN list covered `mac.fritz.box` but not `macoauth.fritz.box`, so the speaker would resolve the OAuth host correctly (via the new DNS hijack) and then immediately fail the TLS handshake — Spotify / Amazon Music token refresh dies before reaching AfterTouch. getDomains now calls discovery.DeriveOAuthHostnames(serverURL) and discovery.DeriveOAuthHostnames(httpsServerURL), feeding the derived names into the SAN map alongside the existing entries. IP-based serverURLs continue to produce no derivation (the OAuth construction is unrecoverable for them — see the existing oauth_target_reachable health check). Tests in cmd/soundtouch-service/main_test.go lock in: - Hostname serverURL → derived OAuth variant present in SAN list. - IP serverURL → no malformed `192oauth.…` entry leaks in. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
90a8bb25cf
commit
93248659a5
@@ -747,6 +747,22 @@ func getDomains(serverURL, httpsServerURL, hostname string, extraHosts []string)
|
||||
domainsMap[strings.ToLower(u.Hostname())] = true
|
||||
}
|
||||
|
||||
// The speaker firmware constructs the OAuth host by appending `oauth`
|
||||
// to the first label of the streaming hostname (see issue #337 and
|
||||
// pkg/discovery/dns.go DeriveOAuthHostnames). The DNS hijack catches
|
||||
// it; the TLS cert must also cover it, otherwise the speaker rejects
|
||||
// the handshake and Spotify / Amazon Music OAuth dies before reaching
|
||||
// AfterTouch. Derive once from each of serverURL and httpsServerURL —
|
||||
// they typically share a hostname but a multi-homed deployment may
|
||||
// differ.
|
||||
for _, h := range discovery.DeriveOAuthHostnames(serverURL) {
|
||||
domainsMap[h] = true
|
||||
}
|
||||
|
||||
for _, h := range discovery.DeriveOAuthHostnames(httpsServerURL) {
|
||||
domainsMap[h] = true
|
||||
}
|
||||
|
||||
// Explicit overrides / additions for multi-homed hosts, reverse proxies,
|
||||
// or browsing the admin UI via a LAN IP that isn't part of serverURL.
|
||||
for _, h := range extraHosts {
|
||||
|
||||
@@ -151,3 +151,39 @@ func TestMergeTLSExtraHosts(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDomains_IncludesOAuthDerivation(t *testing.T) {
|
||||
// Hostname-based serverURL: the derived OAuth variant must end up
|
||||
// in the served TLS cert SAN list, otherwise the speaker rejects
|
||||
// the TLS handshake on Spotify / Amazon Music token refresh.
|
||||
got := getDomains("http://mac.fritz.box:8000", "https://mac.fritz.box:8443", "mac.fritz.box", nil)
|
||||
|
||||
want := "macoauth.fritz.box"
|
||||
if !contains(got, want) {
|
||||
t.Errorf("expected SAN list to include %q (derived from serverURL), got: %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDomains_IPServerURLProducesNoOAuthDerivation(t *testing.T) {
|
||||
// IP-based serverURL deliberately yields no derivation (the speaker's
|
||||
// `<first-label>oauth.<rest>` construction would be malformed for an
|
||||
// IP and no DNS resolver can answer for it). The cert SAN list must
|
||||
// not pretend to cover something that can never be queried.
|
||||
got := getDomains("http://192.168.0.30:8000", "https://192.168.0.30:8443", "192.168.0.30", nil)
|
||||
|
||||
for _, h := range got {
|
||||
if h == "192oauth.168.0.30" {
|
||||
t.Errorf("SAN list must not include malformed IP-derived OAuth name, got: %v", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contains(haystack []string, needle string) bool {
|
||||
for _, h := range haystack {
|
||||
if h == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user