Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ProxyAgent sends wrong SNI to proxy #2926

Closed
chrros95 opened this issue Mar 6, 2024 · 2 comments
Closed

ProxyAgent sends wrong SNI to proxy #2926

chrros95 opened this issue Mar 6, 2024 · 2 comments
Labels
bug Something isn't working

Comments

@chrros95
Copy link
Contributor

chrros95 commented Mar 6, 2024

Bug Description

When using an encrypted connection for a proxy with the ProxyAgent the hostname from the actual request is used instead of the hostname from the proxy URI.

Reproducible By

Run the following script and observe that the first client hello contains the hostname of the request URL.

import { ProxyAgent, fetch } from 'undici'
import fs from 'fs/promises'
const proxyHostname = 'proxy.internal'
const requestHostname = 'example.com'
const caPath = '~/ca.pem'
const enableTrace = true

const test = async (): Promise<unknown> => {
    const ca = await fs.readFile(caPath)
    const response = fetch('https://'+requestHostname, {
        dispatcher: new ProxyAgent({
            uri: 'https://'+proxyHostname+':8080',
            connect: { ca, servername: proxyHostname, host: proxyHostname, enableTrace },
            proxyTls: { ca, servername: proxyHostname, host: proxyHostname, enableTrace },
            requestTls: { ca, servername: requestHostname, host: requestHostname, enableTrace }
        })
    })
    return (await response).text()
}
test().then(r => console.log('Respone', r)).catch(e => console.error('fetch failed', e))

Expected Behavior

Expect is that the first client hello contains the SNI name from proxy.

Logs & Screenshots

Example Output
Sent Record
Header:
  Version = TLS 1.0 (0x301)
  Content Type = Handshake (22)
  Length = 379
    ClientHello, Length=375
      client_version=0x303 (TLS 1.2)
      Random:
        gmt_unix_time=0x0DA529B7
        random_bytes (len=28): DA63174BCBFEF26D17359C3A9E7CC94C93DDBCD4A754763905798CB8
      session_id (len=32): 17331EF37F92BB7BD08EF7E3CFD80501113B73D680E82EE9F2DB861DD00CC0BA
      cipher_suites (len=118)
        {0x13, 0x02} TLS_AES_256_GCM_SHA384
        {0x13, 0x03} TLS_CHACHA20_POLY1305_SHA256
        {0x13, 0x01} TLS_AES_128_GCM_SHA256
        {0xC0, 0x2F} TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        {0xC0, 0x2B} TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        {0xC0, 0x30} TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        {0xC0, 0x2C} TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        {0x00, 0x9E} TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
        {0xC0, 0x27} TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
        {0x00, 0x67} TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
        {0xC0, 0x28} TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
        {0x00, 0x6B} TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
        {0x00, 0xA3} TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
        {0x00, 0x9F} TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
        {0xCC, 0xA9} TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
        {0xCC, 0xA8} TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
        {0xCC, 0xAA} TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
        {0xC0, 0xAF} TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8
        {0xC0, 0xAD} TLS_ECDHE_ECDSA_WITH_AES_256_CCM
        {0xC0, 0xA3} TLS_DHE_RSA_WITH_AES_256_CCM_8
        {0xC0, 0x9F} TLS_DHE_RSA_WITH_AES_256_CCM
        {0xC0, 0x5D} TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384
        {0xC0, 0x61} TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384
        {0xC0, 0x57} TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384
        {0xC0, 0x53} TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384
        {0x00, 0xA2} TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
        {0xC0, 0xAE} TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8
        {0xC0, 0xAC} TLS_ECDHE_ECDSA_WITH_AES_128_CCM
        {0xC0, 0xA2} TLS_DHE_RSA_WITH_AES_128_CCM_8
        {0xC0, 0x9E} TLS_DHE_RSA_WITH_AES_128_CCM
        {0xC0, 0x5C} TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256
        {0xC0, 0x60} TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256
        {0xC0, 0x56} TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256
        {0xC0, 0x52} TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256
        {0xC0, 0x24} TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
        {0x00, 0x6A} TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
        {0xC0, 0x23} TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
        {0x00, 0x40} TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
        {0xC0, 0x0A} TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
        {0xC0, 0x14} TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
        {0x00, 0x39} TLS_DHE_RSA_WITH_AES_256_CBC_SHA
        {0x00, 0x38} TLS_DHE_DSS_WITH_AES_256_CBC_SHA
        {0xC0, 0x09} TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
        {0xC0, 0x13} TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
        {0x00, 0x33} TLS_DHE_RSA_WITH_AES_128_CBC_SHA
        {0x00, 0x32} TLS_DHE_DSS_WITH_AES_128_CBC_SHA
        {0x00, 0x9D} TLS_RSA_WITH_AES_256_GCM_SHA384
        {0xC0, 0xA1} TLS_RSA_WITH_AES_256_CCM_8
        {0xC0, 0x9D} TLS_RSA_WITH_AES_256_CCM
        {0xC0, 0x51} TLS_RSA_WITH_ARIA_256_GCM_SHA384
        {0x00, 0x9C} TLS_RSA_WITH_AES_128_GCM_SHA256
        {0xC0, 0xA0} TLS_RSA_WITH_AES_128_CCM_8
        {0xC0, 0x9C} TLS_RSA_WITH_AES_128_CCM
        {0xC0, 0x50} TLS_RSA_WITH_ARIA_128_GCM_SHA256
        {0x00, 0x3D} TLS_RSA_WITH_AES_256_CBC_SHA256
        {0x00, 0x3C} TLS_RSA_WITH_AES_128_CBC_SHA256
        {0x00, 0x35} TLS_RSA_WITH_AES_256_CBC_SHA
        {0x00, 0x2F} TLS_RSA_WITH_AES_128_CBC_SHA
        {0x00, 0xFF} TLS_EMPTY_RENEGOTIATION_INFO_SCSV
      compression_methods (len=1)
        No Compression (0x00)
      extensions, length = 184
        extension_type=server_name(0), length=16                                  << I expect
          0000 - 00 0e 00 00 0b 65 78 61-6d 70 6c 65 2e 63 6f   .....example.co    << proxy.internal 
          000f - 6d                                             m                    << here
        extension_type=ec_point_formats(11), length=4
          uncompressed (0)
          ansiX962_compressed_prime (1)
          ansiX962_compressed_char2 (2)
        extension_type=supported_groups(10), length=22
          ecdh_x25519 (29)
          secp256r1 (P-256) (23)
          ecdh_x448 (30)
          secp521r1 (P-521) (25)
          secp384r1 (P-384) (24)
          ffdhe2048 (256)
          ffdhe3072 (257)
          ffdhe4096 (258)
          ffdhe6144 (259)
          ffdhe8192 (260)
        extension_type=session_ticket(35), length=0
        extension_type=application_layer_protocol_negotiation(16), length=11
          http/1.1
        extension_type=encrypt_then_mac(22), length=0
        extension_type=extended_master_secret(23), length=0
        extension_type=signature_algorithms(13), length=42
          ecdsa_secp256r1_sha256 (0x0403)
          ecdsa_secp384r1_sha384 (0x0503)
          ecdsa_secp521r1_sha512 (0x0603)
          ed25519 (0x0807)
          ed448 (0x0808)
          rsa_pss_pss_sha256 (0x0809)
          rsa_pss_pss_sha384 (0x080a)
          rsa_pss_pss_sha512 (0x080b)
          rsa_pss_rsae_sha256 (0x0804)
          rsa_pss_rsae_sha384 (0x0805)
          rsa_pss_rsae_sha512 (0x0806)
          rsa_pkcs1_sha256 (0x0401)
          rsa_pkcs1_sha384 (0x0501)
          rsa_pkcs1_sha512 (0x0601)
          ecdsa_sha224 (0x0303)
          rsa_pkcs1_sha224 (0x0301)
          dsa_sha224 (0x0302)
          dsa_sha256 (0x0402)
          dsa_sha384 (0x0502)
          dsa_sha512 (0x0602)
        extension_type=supported_versions(43), length=5
          TLS 1.3 (772)
          TLS 1.2 (771)
        extension_type=psk_key_exchange_modes(45), length=2
          psk_dhe_ke (1)
        extension_type=key_share(51), length=38
            NamedGroup: ecdh_x25519 (29)
            key_exchange:  (len=32): 98B6544EAFE554DF8119970FE13A5555895668249400330F5DDDD4B821590214

Received Record
Header:
Version = TLS 1.2 (0x303)
Content Type = Handshake (22)
Length = 122
ServerHello, Length=118
server_version=0x303 (TLS 1.2)
Random:
gmt_unix_time=0x06CBA8C8
random_bytes (len=28): A02145755E1FE3E03A7C9E25D10A59AD74540932172C2682B6CDA595
session_id (len=32): 17331EF37F92BB7BD08EF7E3CFD80501113B73D680E82EE9F2DB861DD00CC0BA
cipher_suite {0x13, 0x01} TLS_AES_128_GCM_SHA256
compression_method: No Compression (0x00)
extensions, length = 46
extension_type=supported_versions(43), length=2
TLS 1.3 (772)
extension_type=key_share(51), length=36
NamedGroup: ecdh_x25519 (29)
key_exchange: (len=32): 9B70EE06D06FAD20478885A21F07491341C211A129D9B51D850116AF8F80A903

Received Record
Header:
Version = TLS 1.2 (0x303)
Content Type = ChangeCipherSpec (20)
Length = 1
Received Record
Header:
Version = TLS 1.2 (0x303)
Content Type = ApplicationData (23)
Length = 38
Inner Content Type = Handshake (22)
EncryptedExtensions, Length=17
extensions, length = 15
extension_type=application_layer_protocol_negotiation(16), length=11
http/1.1

Received Record
Header:
Version = TLS 1.2 (0x303)
Content Type = ApplicationData (23)
Length = 896
Inner Content Type = Handshake (22)
Certificate, Length=875
context (len=0):
certificate_list, length=871
ASN.1Cert, length=866
------details-----
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
e1:a1:b2:9b:cb:f7:7f:6c:8d:19:b7:79:24:d4:d7:5b
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = TRAEFIK DEFAULT CERT
Validity
Not Before: Mar 5 20:38:57 2024 GMT
Not After : Mar 5 20:38:57 2025 GMT
Subject: CN = TRAEFIK DEFAULT CERT
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:ab:45:e5:b7:3e:4c:44:28:cf:be:ec:c5:27:fb:
98:83:a6:1d:d2:33:86:16:ca:56:2f:4a:86:5f:7c:
ef:22:c5:3d:ba:15:8a:bb:8d:23:81:e7:96:24:87:
08:4b:aa:be:4a:1a:e1:14:38:61:a6:27:ec:55:79:
5f:ba:ec:f5:9e:ca:19:f9:3e:68:1e:76:89:6f:42:
8c:0a:19:ca:5e:d8:80:20:5f:ef:08:49:b7:1b:b4:
73:e5:81:df:02:bd:a3:47:2d:dd:71:d1:c1:f9:4d:
4c:0e:b0:e6:e0:f6:f2:41:5e:53:8a:2a:52:41:3f:
61:1e:4e:78:1d:f4:14:ef:2a:50:e8:88:b8:b8:9c:
c2:a5:d5:f9:b5:8e:bd:ea:2f:7d:fb:1f:33:03:96:
7c:3e:e0:6b:3e:c7:02:54:01:e8:0d:16:16:98:f6:
e4:0c:60:0c:1e:0a:66:64:6f:c6:d8:be:5d:63:fa:
3f:89:d3:22:cc:ec:c9:77:8b:2a:0d:56:1b:ae:92:
c4:f6:7a:08:e4:de:4e:9e:bd:d9:65:a3:b5:86:10:
a5:68:cc:dc:ce:94:70:11:5b:85:ac:7f:6b:b3:dc:
6d:0e:71:4c:28:d1:b9:46:c9:50:42:10:a4:22:20:
c8:5c:9c:d1:af:3e:0d:41:8e:d6:fd:a0:db:b1:24:
37:e3
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment, Data Encipherment, Key Agreement
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Alternative Name:
DNS:5ee1ac8c84cad765fe44ef7ccd47dec3.74c4aef601a7ad8758f94566a17ae0b7.traefik.default
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
a0:3d:91:02:dd:ae:82:ab:53:42:36:35:e1:d3:cd:a2:af:a9:
3c:23:59:f3:8b:82:6d:54:04:e5:92:49:e0:78:fb:ad:23:36:
0d:82:10:f0:d8:17:5f:6c:9c:84:20:bb:38:32:fd:aa:ab:ef:
13:67:d5:23:82:bd:5f:6f:3b:77:66:1c:d9:18:fc:66:64:95:
21:ca:74:54:4e:a4:94:2e:b5:ed:88:f6:5b:4a:af:3b:8f:d5:
7b:09:a4:91:e1:b1:10:c9:b4:ee:71:d9:d1:ae:e1:d7:70:dd:
8c:c7:dd:fa:25:de:50:14:17:91:92:53:49:ac:86:32:e9:14:
94:6b:55:da:58:23:11:16:e0:e2:8c:6a:69:32:d2:04:93:27:
f9:28:67:7d:e4:56:56:b8:e1:08:00:df:97:97:e4:6c:7f:0f:
95:c7:82:9d:69:ec:0a:78:38:36:f4:19:c3:d6:c7:9b:a7:e8:
64:20:10:c7:46:00:e7:7e:db:b8:36:8a:bb:4c:4d:f7:78:e1:
f1:1b:cd:bf:c2:3c:9f:14:25:de:f9:d6:36:78:00:90:13:e5:
28:02:2a:b3:d7:5e:be:79:db:ec:9c:b9:a3:c7:7b:79:95:c5:
99:af:1f:1a:3b:19:62:e7:a4:11:25:d1:4a:14:d3:a7:79:ac:
d4:bb:65:0a
-----BEGIN CERTIFICATE-----
MIIDXjCCAkagAwIBAgIRAOGhspvL939sjRm3eSTU11swDQYJKoZIhvcNAQELBQAw
HzEdMBsGA1UEAxMUVFJBRUZJSyBERUZBVUxUIENFUlQwHhcNMjQwMzA1MjAzODU3
WhcNMjUwMzA1MjAzODU3WjAfMR0wGwYDVQQDExRUUkFFRklLIERFRkFVTFQgQ0VS
VDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKtF5bc+TEQoz77sxSf7
mIOmHdIzhhbKVi9Khl987yLFPboViruNI4HnliSHCEuqvkoa4RQ4YaYn7FV5X7rs
9Z7KGfk+aB52iW9CjAoZyl7YgCBf7whJtxu0c+WB3wK9o0ct3XHRwflNTA6w5uD2
8kFeU4oqUkE/YR5OeB30FO8qUOiIuLicwqXV+bWOveovffsfMwOWfD7gaz7HAlQB
6A0WFpj25AxgDB4KZmRvxti+XWP6P4nTIszsyXeLKg1WG66SxPZ6COTeTp692WWj
tYYQpWjM3M6UcBFbhax/a7PcbQ5xTCjRuUbJUEIQpCIgyFyc0a8+DUGO1v2g27Ek
N+MCAwEAAaOBlDCBkTAOBgNVHQ8BAf8EBAMCA7gwEwYDVR0lBAwwCgYIKwYBBQUH
AwEwDAYDVR0TAQH/BAIwADBcBgNVHREEVTBTglE1ZWUxYWM4Yzg0Y2FkNzY1ZmU0
NGVmN2NjZDQ3ZGVjMy43NGM0YWVmNjAxYTdhZDg3NThmOTQ1NjZhMTdhZTBiNy50
cmFlZmlrLmRlZmF1bHQwDQYJKoZIhvcNAQELBQADggEBAKA9kQLdroKrU0I2NeHT
zaKvqTwjWfOLgm1UBOWSSeB4+60jNg2CEPDYF19snIQguzgy/aqr7xNn1SOCvV9v
O3dmHNkY/GZklSHKdFROpJQute2I9ltKrzuP1XsJpJHhsRDJtO5x2dGu4ddw3YzH
3fol3lAUF5GSU0mshjLpFJRrVdpYIxEW4OKMamky0gSTJ/koZ33kVla44QgA35eX
5Gx/D5XHgp1p7Ap4ODb0GcPWx5un6GQgEMdGAOd+27g2irtMTfd44fEbzb/CPJ8U
Jd751jZ4AJAT5SgCKrPXXr552+ycuaPHe3mVxZmvHxo7GWLnpBEl0UoU06d5rNS7
ZQo=
-----END CERTIFICATE-----

    No extensions

Received Record
Header:
Version = TLS 1.2 (0x303)
Content Type = ApplicationData (23)
Length = 281
Inner Content Type = Handshake (22)
CertificateVerify, Length=260
Signature Algorithm: rsa_pss_rsae_sha256 (0x0804)
Signature (len=256): 063E55F5F6FA34450CF12DDECB3AF0DA37670FC69B60AEBFB6A5A809A39F4D28CC8E8B0115A53F3335A399E53D4B5CDD2B25AFA55E988A34D41FC7A3A4772DC97A83B0A0C05E672CBD0EAD09022ED3BA32940FFB46B5E0784B3608C83EABFDB45C161F6E96A38D01611F0C31AB5E993BEAC6C6036DA5E0748A5945F2DBE3F82A098A25A41386807A532986D100EE582D25B82273839E7BE6D02141FBD1F1C728EC2EE5DB1B195281035101E5E88E3B5677832837FCACD27B8342305E35CD5BC4A35EC8A1E9C6B115172AD65CC4383C97DBB86690286B4D0CE2BE8038BB9FE961920A92FC4466E0C4A15B1252E9BBFAA39F2FD135435DA21EF366931263C0A0AB

Received Record
Header:
Version = TLS 1.2 (0x303)
Content Type = ApplicationData (23)
Length = 53
Inner Content Type = Handshake (22)
Finished, Length=32
verify_data (len=32): 5E8F9ACE5560F2D1FCF05B383BCBA0D9629472B2E9FEC13FCCDD64002E48F394

Sent Record
Header:
Version = TLS 1.2 (0x303)
Content Type = ChangeCipherSpec (20)
Length = 1
change_cipher_spec (1)

Sent Record
Header:
Version = TLS 1.2 (0x303)
Content Type = ApplicationData (23)
Length = 53
Inner Content Type = Handshake (22)
Finished, Length=32
verify_data (len=32): 2F0ED8A1804E61E620C7B2B98F2ACCB02C195F4019B6A5B5A0F11003C48C5685

fetch failed TypeError: fetch failed
at fetch (/home/user/git/pld/node_modules/.pnpm/undici@6.6.2/node_modules/undici/index.js:103:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
[cause]: Error: self-signed certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1674:34)
at TLSSocket.emit (node:events:518:28)
at TLSSocket.emit (node:domain:488:12)
at TLSSocket._finishInit (node:_tls_wrap:1085:8)
at ssl.onhandshakedone (node:_tls_wrap:871:12) {
code: 'DEPTH_ZERO_SELF_SIGNED_CERT'
}
}
Received Record
Header:
Version = TLS 1.2 (0x303)
Content Type = ApplicationData (23)
Length = 139

Environment

undici@6.6.2
node v20.11.1
Pop!_OS 22.04 LTS

Additional context

A hacky workaround that allowed my setup to work, is to ignore the servername from line 91 in the connect.js if the httpSocket is not defined

      if(httpSocket === undefined){
        servername = options.servername ||  util.getServerName(host) || null
      }

undici/lib/core/connect.js

Lines 91 to 101 in 03a2d43

servername = servername || options.servername || util.getServerName(host) || null
const sessionKey = servername || hostname
const session = sessionCache.get(sessionKey) || null
assert(sessionKey)
socket = tls.connect({
highWaterMark: 16384, // TLS in node can't have bigger HWM anyway...
...options,
servername,

@chrros95 chrros95 added the bug Something isn't working label Mar 6, 2024
@Uzlopak Uzlopak changed the title ProxyAgent sends wrong SNI to proxy fetch: ProxyAgent sends wrong SNI to proxy Mar 6, 2024
@KhafraDev KhafraDev changed the title fetch: ProxyAgent sends wrong SNI to proxy ProxyAgent sends wrong SNI to proxy Mar 6, 2024
@metcoder95
Copy link
Member

Yeah, sounds fair, that can cause potential side effects on TLS negotiations.
Though, I'll try so scope this within ProxyAgent instead of directly pointing to connect.js.

would you like to send a PR to fix that? Do not forget the unit testing 🙂

chrros95 added a commit to chrros95/undici that referenced this issue Mar 9, 2024
resolves nodejs#2926.

With this fix the correct server name indicator (SNI) should be send to a proxy.
The SNI extensions allows the proxy to select between different TLS profiles

To avoid a loop in the connect method of the client a new error is introduced.
@chrros95
Copy link
Contributor Author

@metcoder95 Thanks for your advice.

Close this as PR is merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants