@@ -27,11 +27,11 @@ type CA struct {
2727
2828// Provider is a domain fronting provider (e.g. Akamai, CloudFront).
2929type Provider struct {
30- HostAliases map [string ]string `yaml:"hostaliases"`
31- PassthroughPatterns []string `yaml:"passthrupatterns"`
32- TestURL string `yaml:"testurl"`
33- Masquerades []* Masquerade `yaml:"masquerades"`
34- VerifyHostname * string `yaml:"verifyhostname"`
30+ HostAliases map [string ]string `yaml:"hostaliases"`
31+ PassthroughPatterns []string `yaml:"passthrupatterns"`
32+ TestURL string `yaml:"testurl"`
33+ Masquerades []* Masquerade `yaml:"masquerades"`
34+ VerifyHostname * string `yaml:"verifyhostname"`
3535 // Pipeline-emitted YAML keys are lowercase-concatenated, not
3636 // snake_case (the upstream generator uses lowercased Go field
3737 // names with no yaml tag); the tag here must match the wire
@@ -133,9 +133,11 @@ func (cfg *Config) CertPool() (*x509.CertPool, error) {
133133 return pool , nil
134134}
135135
136- // ExpandedProvider returns a copy of the provider with masquerades expanded
137- // with SNI based on the country code. Host aliases are lowercased.
138- // Passthrough patterns are also lowercased for efficient lookup.
136+ // ExpandedProvider returns a copy of the provider with each masquerade's SNI
137+ // resolved: a country-specific or "default" arbitrary SNI if the provider
138+ // configures one (the "default" strategy applies even with no country code),
139+ // otherwise the masquerade's baked-in SNI, otherwise empty (SNI omitted). Host
140+ // aliases and passthrough patterns are lowercased for efficient lookup.
139141func ExpandedProvider (p * Provider , countryCode string ) * Provider {
140142 ep := & Provider {
141143 HostAliases : make (map [string ]string , len (p .HostAliases )),
@@ -154,8 +156,13 @@ func ExpandedProvider(p *Provider, countryCode string) *Provider {
154156 ep .PassthroughPatterns [i ] = strings .ToLower (pt )
155157 }
156158
159+ // Select the SNI strategy: a country-specific entry if one matches, else the
160+ // "default" entry. The default applies even when no country code is set, so a
161+ // provider's default arbitrary-SNI strategy is active for every client — the
162+ // production client passes no country code, and gating "default" behind one
163+ // would leave the strategy permanently inert.
157164 var sniCfg * SNIConfig
158- if countryCode != "" && p .FrontingSNIs != nil {
165+ if p .FrontingSNIs != nil {
159166 var ok bool
160167 sniCfg , ok = p .FrontingSNIs [countryCode ]
161168 if ! ok {
@@ -164,13 +171,41 @@ func ExpandedProvider(p *Provider, countryCode string) *Provider {
164171 }
165172
166173 for _ , m := range p .Masquerades {
167- sni := GenerateSNI (sniCfg , m .IpAddress )
168- ep .Masquerades = append (ep .Masquerades , & Masquerade {
174+ // A generated SNI (country-specific or "default" arbitrary-SNI strategy)
175+ // takes precedence. Otherwise keep any SNI baked into the masquerade by
176+ // the config — this lets a provider whose edges require a specific front
177+ // SNI pin one per masquerade without depending on a country code being
178+ // set (the production client sets none). Empty stays empty (SNI omitted).
179+ sni := m .SNI
180+ if g := GenerateSNI (sniCfg , m .IpAddress ); g != "" {
181+ sni = g
182+ }
183+ nm := & Masquerade {
169184 Domain : m .Domain ,
170185 IpAddress : m .IpAddress ,
171186 SNI : sni ,
172- VerifyHostname : p .VerifyHostname ,
173- })
187+ VerifyHostname : m .VerifyHostname ,
188+ }
189+ // Resolve the hostname the edge cert is verified against on the SNI path
190+ // (dialFront): a per-masquerade value wins, then the provider default,
191+ // and finally the front Domain. Defaulting to Domain matters because the
192+ // SNI path otherwise falls back to chain-only verification when no
193+ // hostname is set — accepting any cert that chains to a trusted root
194+ // (for a single-CA pool like aliyun's GlobalSign R3, any R3-issued cert,
195+ // which a network MITM could present). We verify against Domain, NOT the
196+ // SNI: the SNI is often a decoy the served cert doesn't cover (akamai
197+ // edges send SNI=crunchbase.com but serve their a248.e.akamai.net cert),
198+ // whereas the cert IS valid for the front Domain — the same check the
199+ // no-SNI path already does.
200+ if nm .VerifyHostname == nil {
201+ nm .VerifyHostname = p .VerifyHostname
202+ }
203+ if nm .VerifyHostname == nil && sni != "" && nm .Domain != "" {
204+ // Point at the new masquerade's own Domain field rather than a
205+ // loop-local copy, avoiding a per-iteration heap allocation.
206+ nm .VerifyHostname = & nm .Domain
207+ }
208+ ep .Masquerades = append (ep .Masquerades , nm )
174209 }
175210 return ep
176211}
0 commit comments