envoyproxy / envoyproxy/envoy

Add API to specify server name for certificate validation for Envoy Mobile

Open
#42,294 1 comment 0 reactions 1 assignee Claimed by @abeyad View on GitHub
area/tls_sni enhancement mobile no stalebot
Dominant language
C++
Stars
28.9k
Forks
5.6k
Avg merge
1d 20h
Merged PRs (30d)
437

Description

**Description**:

Envoy Mobile currently lacks the ability to validate TLS certificates against a hostname when connecting to an IP address. While setUpstreamTlsSni() sets the SNI during TLS handshake, certificate validation still fails when using IP-based URLs because the validation occurs against the URL host (the IP) rather than the SNI value. Previously the issue was discussed here https://github.com/envoyproxy/envoy/issues/41685.

**Desired Behavior**:

Add an API to EngineBuilder that allows applications to specify the server name used for certificate validation, independent of the connection URL:

This would enable applications to:

1. Connect to backend servers via IP addresses (provided by VPN/network configuration)
2. Validate TLS certificates against the proper domain name (matching certificate CN/SAN)
3. Avoid insecure setTrustChainVerification(ACCEPT_UNTRUSTED) workaround that allows MITM attacks
4. Support private/self-signed certificates without disabling security

**Use Case Scenario**:

A mobile application connects to a backend server. The setup includes:

- Backend's domain name is not in public DNS
- Backend uses self-signed certificate with domain name "some.common.name" as CN instead of ip "ip_address": CN=some.common.name

Currently, there are only two options:

- **Connect using hostname URL** (https://some.common.name) → DNS resolution fails (domain not in public DNS)
- **Connect using IP URL** (https://ip_address) → Certificate validation fails with error: SSL: no alternative certificate subject name matches target ipv4 address

**Current Workaround (Insecure)**:

As @abeyad suggested here https://github.com/envoyproxy/envoy/issues/41685#issuecomment-3469453376, use `EngineBuilder.setTrustChainVerification(ACCEPT_UNTRUSTED)`.
This workaround defeats the purpose of TLS certificate validation and is only marked for "test purposes" in the API documentation.

**Attempted Solutions That Don't Work**:

- setUpstreamTlsSni("backend.drone.id") - Sets SNI header but certificate validation still checks against IP URL
- addDNSPreresolveHostnames() - Only pre-warms DNS cache, doesn't support custom IP mappings
- Setting Host header manually - Blocked by isRestrictedHeader() validation
- Custom network-security-config.xml - Cannot add IP-based certificate pinning for domain certificates

**Error Details**:

When connecting to IP with domain-based certificate:
```
Error: rc: 503|ec: 2|rsp_flags: 5|http: 1|det: upstream_reset_before_response_started{remote_connection_failure|delayed_connect_error:_Connection_refused}
```

Equivalent curl output shows the real issue:
```
SSL: no alternative certificate subject name matches target ipv4 address ''
QUIC connect failed: SSL peer certificate or SSH remote key was not OK
```

**Comparison with Similar Libraries**:
**OkHttp3**:
It solves this with a custom `[Dns](https://square.github.io/okhttp/3.x/okhttp/okhttp3/Dns.html)` interface that allows hostname-to-IP mappings while preserving certificate validation:
```
val client = OkHttpClient.Builder()
.dns(object : Dns {
override fun lookup(hostname: String): List {
if (hostname == "backend.drone.id") {
return listOf(InetAddress.getByName("10.140.252.161"))
}
return Dns.SYSTEM.lookup(hostname)
}
})
.build()
// Connects to IP but validates certificate against hostname - secure ✅
```

**Cloudflare quiche**:

Cloudflare's quiche library solves this with the `server_name` parameter in `[quiche::connect()](https://docs.rs/quiche/latest/quiche/fn.connect.html)`:

> "the optional server_name parameter is used to verify the peer's certificate"

This allows:

- Connecting to an IP address (actual network connection target)
- Validating certificate against a domain name (proper security validation)
- Maintaining security while supporting VPN/private network scenarios

**Proposed API**:

Option 1 - Explicit API (clearest intent):
```
fun setServerNameForCertificateValidation(serverName: String): EngineBuilder
```

Option 2 - Fix setUpstreamTlsSni() to also apply to certificate validation:
```
// Current behavior: only sets SNI, doesn't affect cert validation
// Proposed: set SNI AND use for certificate validation
fun setUpstreamTlsSni(sni: String): EngineBuilder
```

Option 3 - Custom DNS API that automatically handles cert validation:
```
fun addDnsMapping(hostname: String, ipAddress: String): EngineBuilder
// Internally: resolves hostname to IP while validating certs against hostname
```

Relevant Links:

- Previous discussion of this issue: https://github.com/envoyproxy/envoy/issues/41685
- OkHttp DNS interface: https://square.github.io/okhttp/3.x/okhttp/okhttp3/Dns.html
- Cloudflare quiche server_name parameter: https://docs.rs/quiche/latest/quiche/fn.connect.html
- Related PR that modified SNI behavior: https://github.com/envoyproxy/envoy/pull/36903

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.