Skip to content

Improve TLS handshake errors surfaced to fetch() #5286

@ainergiz

Description

@ainergiz

While having a look at this issue reported in workers-sdk, I've pointed the url to eg. https://self-signed.badssl.com/ (serves self-signed/untrusted certificate) to reproduce the error and received this:

Error: internal error; reference = asv0lnabm9mht2l97b8uim0n
    at async Object.fetch (file:///Users/ainergiz/LocalStorage/Documents/cosmo-app/cf-request-error/src/index.ts:3:13)
    at async jsonError (file:///Users/ainergiz/LocalStorage/Documents/cosmo-app/cf-request-error/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts:22:10)
    at async drainBody (file:///Users/ainergiz/LocalStorage/Documents/cosmo-app/cf-request-error/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts:5:10)%    

so the root cause (TLS trust failure) never reaches the user

proposal:

return a TypeError (or similar) including the TLS failure reason, e.g.

  TypeError: TLS certificate validation failed; reason: self signed certificate.
  If you're intercepting HTTPS locally, add the signing root certificate to NODE_EXTRA_CA_CERTS or SSL_CERT_FILE before running wrangler dev.

proposed change here:

In fetchImplNoOutputLock, detect when the kj::Exception message contains TLS peer's certificate is not trusted. When that happens:

       } else if (exception.getDescription().contains("TLS peer's certificate is not trusted")) {
          auto desc = exception.getDescription();
          constexpr kj::StringPtr reasonPrefix = "reason = "_kj;
          kj::StringPtr reason = ""_kj;
          KJ_IF_SOME(pos, desc.find(reasonPrefix)) {
            reason = desc.slice(pos + reasonPrefix.size());
          }
          if (reason.size() > 0) {
            return JSG_KJ_EXCEPTION(FAILED, TypeError,
                kj::str("TLS certificate validation failed; reason: ", reason,
                    ". Disable the VPN/proxy or add its signing certificate to "
                    "NODE_EXTRA_CA_CERTS (or SSL_CERT_FILE) before running wrangler dev."));
          }
          return JSG_KJ_EXCEPTION(FAILED, TypeError,
              kj::str("TLS certificate validation failed. Disable the VPN/proxy or add its signing "
                      "certificate to NODE_EXTRA_CA_CERTS (or SSL_CERT_FILE) before running "
                      "wrangler dev."));

I've done this change and built it locally which resulted with this error when fetching that same badssl url:

TypeError: TLS certificate validation failed; reason: self signed certificate. If you're intercepting HTTPS locally, add the signing root certificate to NODE_EXTRA_CA_CERTS or SSL_CERT_FILE before running wrangler dev.
    at async Object.fetch (file:///Users/ainergiz/LocalStorage/Documents/cosmo-app/cf-request-error/src/index.ts:3:13)
    at async jsonError (file:///Users/ainergiz/LocalStorage/Documents/cosmo-app/cf-request-error/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts:22:10)
    at async drainBody (file:///Users/ainergiz/LocalStorage/Documents/cosmo-app/cf-request-error/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts:5:10)%   

I think this can be a straightforward PR but wanted to post it here first.

I’ve also prepared a matching patch for workers-sdk that detects the same runtime log line and prints a concise hint in the terminal, so the issue is clear even on older workerd builds. Happy to post that PR once this one lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions