nodejs / nodejs/undici

ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE / ERR_TLS_CERT_ALTNAME_INVALID with dns interceptor and requestTls

Open
#3,988 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
JavaScript
Stars
7.7k
Forks
880
Avg merge
2d 16h
Merged PRs (30d)
68

Description

Bug Description

When using the requestTls option (specifically rejectUnauthorized) in conjunction with the DNS interceptor (interceptors.dns()), multiple SSL errors are thrown. The behavior changes depending on the configuration:

  • If I remove .compose([interceptors.dns()]), the issue disappears, and everything works fine.
  • If I remove requestTls completely, it also works fine even with .compose([interceptors.dns()]).
  • If I keep requestTls but remove only rejectUnauthorized: true (just { requestTls: {} } ), the error persists when .compose([interceptors.dns()]) is used.

This suggests an interaction between requestTls and the DNS interceptor that causes SSL issues.

Reproducible By

Example 1: (issue)

import { ProxyAgent, Agent, fetch, interceptors } from 'undici'

async function main () {
  await fetch('https://api.ipify.org', {
    dispatcher: new Agent()
    .compose([interceptors.dns()])
  }).then(async x => console.log('My IP:', await x.text()))
  
  await fetch('https://api.ipify.org', {
    dispatcher: new ProxyAgent({
      uri: 'http://localhost:18080'
    })
    .compose([interceptors.dns()])
  }).then(async x => console.log('Proxy IP:', await x.text()))

  const response = await fetch('https://api.ipify.org', {
    dispatcher: new ProxyAgent({
      uri: 'http://localhost:18080',
      requestTls: {
        rejectUnauthorized: true
      }
    })
    .compose([interceptors.dns()])
  }).then(x => x.text())
  
  console.log('Ip with rejectUnauthorized: true', response)
}

main ()

Result:

My IP: [REDACTED].212.[REDACTED].76
Proxy IP: [REDACTED].111.[REDACTED].50
/home/[REDACTED]/undici-altname-ssl-issue/node_modules/undici/index.js:120
      Error.captureStackTrace(err)
            ^

TypeError: fetch failed
    at fetch (/home/[REDACTED]/undici-altname-ssl-issue/node_modules/undici/index.js:120:13)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async main (file:///home/[REDACTED]/undici-altname-ssl-issue/index.js:16:20) {
  [cause]: [Error: 40C8661E52720000:error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1590:SSL alert number 40
  ] {
    library: 'SSL routines',
    reason: 'sslv3 alert handshake failure',
    code: 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'
  }
}

Example two: (issue)

import { ProxyAgent, Agent, fetch, interceptors } from 'undici'

async function main () {
  await fetch('https://api.ipify.org', {
    dispatcher: new Agent()
    .compose([interceptors.dns()])
  }).then(async x => console.log('My IP:', await x.text()))
  
  await fetch('https://api.ipify.org', {
    dispatcher: new ProxyAgent({
      uri: 'http://localhost:18080'
    })
    .compose([interceptors.dns()])
  }).then(async x => console.log('Proxy IP:', await x.text()))

  const response = await fetch('https://wikipedia.org', {
    dispatcher: new ProxyAgent({
      uri: 'http://localhost:18080',
      requestTls: {
        rejectUnauthorized: true
      }
    })
    .compose([interceptors.dns()])
  }).then(x => x.text())
  
  console.log(response)
}

main ()

Result:

Click me to show large log
My IP: [REDACTED].212.[REDACTED].76
Proxy IP: [REDACTED].111.[REDACTED].50
/home/[REDACTED]/undici-altname-ssl-issue/node_modules/undici/index.js:120
      Error.captureStackTrace(err)
            ^

TypeError: fetch failed
    at fetch (/home/[REDACTED]/undici-altname-ssl-issue/node_modules/undici/index.js:120:13)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async main (file:///home/[REDACTED]/undici-altname-ssl-issue/index.js:16:20) {
  [cause]: Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames: IP: 185.15.59.224 is not in the cert's list: 
      at Object.checkServerIdentity (node:tls:337:12)
      at TLSSocket.onConnectSecure (node:_tls_wrap:1684:27)
      at TLSSocket.emit (node:events:518:28)
      at TLSSocket._finishInit (node:_tls_wrap:1085:8)
      at ssl.onhandshakedone (node:_tls_wrap:871:12) {
    code: 'ERR_TLS_CERT_ALTNAME_INVALID',
    reason: "IP: 185.15.59.224 is not in the cert's list: ",
    host: '185.15.59.224',
    cert: {
      subject: [Object: null prototype] {
        C: 'US',
        ST: 'California',
        L: 'San Francisco',
        O: 'Wikimedia Foundation, Inc.',
        CN: '*.wikipedia.org'
      },
      issuer: [Object: null prototype] {
        C: 'US',
        O: 'DigiCert Inc',
        CN: 'DigiCert TLS Hybrid ECC SHA384 2020 CA1'
      },
      subjectaltname: 'DNS:*.wikipedia.org, DNS:wikimedia.org, DNS:mediawiki.org, DNS:wikibooks.org, DNS:wikidata.org, DNS:wikinews.org, DNS:wikiquote.org, DNS:wikisource.org, DNS:wikiversity.org, DNS:wikivoyage.org, DNS:wiktionary.org, DNS:wikimediafoundation.org, DNS:w.wiki, DNS:wmfusercontent.org, DNS:*.m.wikipedia.org, DNS:*.wikimedia.org, DNS:*.m.wikimedia.org, DNS:*.planet.wikimedia.org, DNS:*.mediawiki.org, DNS:*.m.mediawiki.org, DNS:*.wikibooks.org, DNS:*.m.wikibooks.org, DNS:*.wikidata.org, DNS:*.m.wikidata.org, DNS:*.wikinews.org, DNS:*.m.wikinews.org, DNS:*.wikiquote.org, DNS:*.m.wikiquote.org, DNS:*.wikisource.org, DNS:*.m.wikisource.org, DNS:*.wikiversity.org, DNS:*.m.wikiversity.org, DNS:*.wikivoyage.org, DNS:*.m.wikivoyage.org, DNS:*.wiktionary.org, DNS:*.m.wiktionary.org, DNS:*.wikimediafoundation.org, DNS:*.wmfusercontent.org, DNS:wikipedia.org, DNS:wikifunctions.org, DNS:*.wikifunctions.org',
      infoAccess: [Object: null prototype] {
        'OCSP - URI': [ 'http://ocsp.digicert.com' ],
        'CA Issuers - URI': [
          'http://cacerts.digicert.com/DigiCertTLSHybridECCSHA3842020CA1-1.crt'
        ]
      },
      ca: false,
      bits: 256,
      pubkey: Buffer(65) [Uint8Array] [
          4,  41, 254, 247,   2, 121, 201, 130, 181,  38,  68,
        233, 201, 191,   6,  62, 207,  73, 162, 210, 234, 254,
         49,  84, 227,  83, 221, 123, 239,  33, 121,  35, 168,
         32, 215,  30,  57, 116, 191,  92,  15, 133, 107, 161,
        108,  81, 133,  72, 194, 184,  17,  16, 168, 195,  45,
        229,  34,   8, 190, 171,  64, 207,  60,  68,  14
      ],
      asn1Curve: 'prime256v1',
      nistCurve: 'P-256',
      valid_from: 'Sep 26 00:00:00 2024 GMT',
      valid_to: 'Oct 17 23:59:59 2025 GMT',
      fingerprint: '0B:3A:AB:D4:5E:55:A4:08:2B:F7:C1:DA:63:37:75:F1:EB:04:6E:A5',
      fingerprint256: '40:62:FB:AE:31:5E:7D:29:B8:24:32:78:9D:DC:4B:99:1D:AB:8B:54:ED:DF:76:C8:12:98:9E:22:F1:BA:FD:59',
      fingerprint512: 'CB:B4:E6:61:67:8C:FC:EB:DE:DD:11:1D:4E:B3:A2:4D:2C:49:3D:16:66:5F:36:F4:17:7A:AA:CA:9B:D7:6B:F3:09:48:7F:1B:1F:67:22:02:2D:4B:7D:A4:FC:A5:E5:4F:1F:60:E3:AB:48:2B:42:CB:E7:45:16:89:E9:A5:DE:64',
      ext_key_usage: [ '1.3.6.1.5.5.7.3.1', '1.3.6.1.5.5.7.3.2' ],
      serialNumber: '0C745DCAE53F59103BEDA2477CCCE73A',
      raw: Buffer(2126) [Uint8Array] [
         48, 130,   8,  74,  48, 130,   7, 207, 160,   3,   2,   1,
          2,   2,  16,  12, 116,  93, 202, 229,  63,  89,  16,  59,
        237, 162,  71, 124, 204, 231,  58,  48,  10,   6,   8,  42,
        134,  72, 206,  61,   4,   3,   3,  48,  86,  49,  11,  48,
          9,   6,   3,  85,   4,   6,  19,   2,  85,  83,  49,  21,
         48,  19,   6,   3,  85,   4,  10,  19,  12,  68, 105, 103,
        105,  67, 101, 114, 116,  32,  73, 110,  99,  49,  48,  48,
         46,   6,   3,  85,   4,   3,  19,  39,  68, 105, 103, 105,
         67, 101, 114, 116,
        ... 2026 more items
      ],
      issuerCertificate: {
        subject: [Object: null prototype] {
          C: 'US',
          O: 'DigiCert Inc',
          CN: 'DigiCert TLS Hybrid ECC SHA384 2020 CA1'
        },
        issuer: [Object: null prototype] {
          C: 'US',
          O: 'DigiCert Inc',
          OU: 'www.digicert.com',
          CN: 'DigiCert Global Root CA'
        },
        infoAccess: [Object: null prototype] {
          'OCSP - URI': [ 'http://ocsp.digicert.com' ],
          'CA Issuers - URI': [ 'http://cacerts.digicert.com/DigiCertGlobalRootCA.crt' ]
        },
        ca: true,
        bits: 384,
        pubkey: Buffer(97) [Uint8Array] [
            4, 193,  27, 198, 154,  91, 152, 217, 164,  41, 160, 233,
          212,   4, 181, 219, 235, 166, 178, 108,  85, 192, 255, 237,
          152, 198,  73,  47,   6,  39,  81, 203, 191, 112, 193,   5,
          122, 195, 177, 157, 135, 137, 186, 173, 180,  19,  23, 201,
          168, 180, 131, 200, 184, 144, 209, 204, 116,  53,  54,  60,
          131, 114, 176, 181, 208, 247,  34, 105, 200, 241, 128, 196,
          123,  64, 143, 207, 104, 135,  38,  92,  57, 137, 241,  77,
          145,  77, 218, 137, 139, 228,   3, 195,  67, 229, 191,  47,
          115
        ],
        asn1Curve: 'secp384r1',
        nistCurve: 'P-384',
        valid_from: 'Apr 14 00:00:00 2021 GMT',
        valid_to: 'Apr 13 23:59:59 2031 GMT',
        fingerprint: 'AE:C1:3C:DD:5E:A6:A3:99:8A:EC:14:AC:33:1A:D9:6B:ED:BB:77:0F',
        fingerprint256: 'F7:A9:A1:B2:FD:96:4A:3F:26:70:BD:66:8D:56:1F:B7:C5:5D:3A:A9:AB:83:91:E7:E1:69:70:2D:B8:A3:DB:CF',
        fingerprint512: 'A9:0D:FF:FB:4B:1C:A3:01:3F:B2:D2:78:3F:AB:A7:B8:03:1E:25:08:08:19:28:63:76:D4:12:EB:97:D3:A5:66:2D:C0:5D:4E:C4:0A:77:29:89:72:0D:F8:2A:7B:67:92:65:56:6D:13:75:F0:0C:85:50:C6:83:03:B8:6A:C0:35',
        ext_key_usage: [ '1.3.6.1.5.5.7.3.1', '1.3.6.1.5.5.7.3.2' ],
        serialNumber: '07F2F35C87A877AF7AEFE947993525BD',
        raw: Buffer(1051) [Uint8Array] [
           48, 130,   4,  23,  48, 130,   2, 255, 160,   3,   2,   1,
            2,   2,  16,   7, 242, 243,  92, 135, 168, 119, 175, 122,
          239, 233,  71, 153,  53,  37, 189,  48,  13,   6,   9,  42,
          134,  72, 134, 247,  13,   1,   1,  12,   5,   0,  48,  97,
           49,  11,  48,   9,   6,   3,  85,   4,   6,  19,   2,  85,
           83,  49,  21,  48,  19,   6,   3,  85,   4,  10,  19,  12,
           68, 105, 103, 105,  67, 101, 114, 116,  32,  73, 110,  99,
           49,  25,  48,  23,   6,   3,  85,   4,  11,  19,  16, 119,
          119, 119,  46, 100,
          ... 951 more items
        ],
        issuerCertificate: <ref *1> {
          subject: [Object: null prototype] {
            C: 'US',
            O: 'DigiCert Inc',
            OU: 'www.digicert.com',
            CN: 'DigiCert Global Root CA'
          },
          issuer: [Object: null prototype] {
            C: 'US',
            O: 'DigiCert Inc',
            OU: 'www.digicert.com',
            CN: 'DigiCert Global Root CA'
          },
          ca: true,
          modulus: 'E23BE11172DEA8A4D3A357AA50A28F0B7790C9A2A5EE12CE965B010920CC0193A74E30B753F743C46900579DE28D22DD870640008109CECE1B83BFDFCD3B7146E2D666C705B37627168F7B9E1E957DEEB748A308DAD6AF7A0C3906657F4A5D1FBC17F8ABBEEE28D7747F7A78995985686E5C23324BBF4EC0E85A6DE370BF7710BFFC01F685D9A844105832A97518D5D1A2BE47E2276AF49A33F84908608BD45FB43A84BFA1AA4A4C7D3ECF4F5F6C765EA04B37919EDC22E66DCE141A8E6ACBFECDB3146417C75B299E32BFF2EEFAD30B42D4ABB74132DA0CD4EFF881D5BB8D583FB51BE84928A270DA3104DDF7B216F24C0A4E07A8ED4A3D5EB57FA390C3AF27',
          bits: 2048,
          exponent: '0x10001',
          pubkey: Buffer(294) [Uint8Array] [
             48, 130,   1,  34,  48,  13,   6,   9,  42, 134,  72, 134,
            247,  13,   1,   1,   1,   5,   0,   3, 130,   1,  15,   0,
             48, 130,   1,  10,   2, 130,   1,   1,   0, 226,  59, 225,
             17, 114, 222, 168, 164, 211, 163,  87, 170,  80, 162, 143,
             11, 119, 144, 201, 162, 165, 238,  18, 206, 150,  91,   1,
              9,  32, 204,   1, 147, 167,  78,  48, 183,  83, 247,  67,
            196, 105,   0,  87, 157, 226, 141,  34, 221, 135,   6,  64,
              0, 129,   9, 206, 206,  27, 131, 191, 223, 205,  59, 113,
             70, 226, 214, 102,
            ... 194 more items
          ],
          valid_from: 'Nov 10 00:00:00 2006 GMT',
          valid_to: 'Nov 10 00:00:00 2031 GMT',
          fingerprint: 'A8:98:5D:3A:65:E5:E5:C4:B2:D7:D6:6D:40:C6:DD:2F:B1:9C:54:36',
          fingerprint256: '43:48:A0:E9:44:4C:78:CB:26:5E:05:8D:5E:89:44:B4:D8:4F:96:62:BD:26:DB:25:7F:89:34:A4:43:C7:01:61',
          fingerprint512: '53:B4:44:E5:65:18:32:01:A6:1E:EB:46:12:09:B2:DC:30:89:5E:EC:A4:87:23:8D:15:A0:26:73:5F:22:9A:81:9E:5B:19:CB:D7:E2:FA:27:68:AB:2A:64:F6:EB:CD:9D:1E:72:13:41:C9:ED:5D:D0:9F:C0:D5:E4:3D:68:BC:A7',
          serialNumber: '083BE056904246B1A1756AC95991C74A',
          raw: Buffer(947) [Uint8Array] [
             48, 130,   3, 175,  48, 130,   2, 151, 160,  3,   2,   1,
              2,   2,  16,   8,  59, 224,  86, 144,  66, 70, 177, 161,
            117, 106, 201,  89, 145, 199,  74,  48,  13,  6,   9,  42,
            134,  72, 134, 247,  13,   1,   1,   5,   5,  0,  48,  97,
             49,  11,  48,   9,   6,   3,  85,   4,   6, 19,   2,  85,
             83,  49,  21,  48,  19,   6,   3,  85,   4, 10,  19,  12,
             68, 105, 103, 105,  67, 101, 114, 116,  32, 73, 110,  99,
             49,  25,  48,  23,   6,   3,  85,   4,  11, 19,  16, 119,
            119, 119,  46, 100,
            ... 847 more items
          ],
          issuerCertificate: [Circular *1]
        }
      }
    }
  }
}

Expected Behavior

The request should succeed without SSL errors when using requestTls and the DNS interceptor together.

Environment

Ubuntu 22.04.5 LTS x86_64
v20.12.2
undici 7.2.0

Additional context

Maybe regression #3817 #3437

This issue occurs whether requestTls is { rejectUnauthorized: true } or simply {}. It appears the combination of requestTls and interceptors.dns() triggers unexpected behavior.

Dumped connection info using MITMProxy

Image
Image
Image

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the supplied JavaScript reproducer with Agent and ProxyAgent, comparing the cases with and without interceptors.dns() and requestTls. Trace the Agent/ProxyAgent requestTls path and the interceptors.dns() entry point; done means the reproduced HTTPS requests no longer produce the reported TLS handshake or certificate-name errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nodejs
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.