nodejs / undici

An HTTP/1.1 client, written from scratch for Node.js
https://nodejs.github.io/undici
MIT License
5.92k stars 513 forks source link

ProxyAgent sends wrong SNI to proxy #2926

Closed chrros95 closed 3 months ago

chrros95 commented 4 months ago

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
      }

https://github.com/nodejs/undici/blob/03a2d439dce5a3821f459e490d5edb691cd590a5/lib/core/connect.js#L91-L101

metcoder95 commented 4 months ago

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 commented 3 months ago

@metcoder95 Thanks for your advice.

Close this as PR is merged.