Skip to content

DSchau/6354-invalid-cert

Repository files navigation

Reproduction: Incomplete SSL Certificate Chain Issue

This repository reproduces postmanlabs/postman-app-support#6354 - the "unable to verify the first certificate" error that occurs when servers don't send intermediate CA certificates.

The Problem

When a server is configured with an incomplete certificate chain (missing intermediate CA certificates), different tools behave differently:

  • cURL: Often works (may fetch intermediate certs or use lenient validation)
  • Browsers: Often work (cache intermediate certs or fetch them automatically)
  • Node.js (strict SSL): Fails with "unable to verify the first certificate"
  • Postman (SSL verification on): Fails with the same error

Prerequisites

  • Node.js installed
  • OpenSSL installed

Quick Start

1. Generate Certificates

chmod +x generate-certs.sh
./generate-certs.sh

This creates:

  • certs/root-ca.crt - Root CA certificate
  • certs/intermediate-ca.crt - Intermediate CA certificate (the missing link!)
  • certs/server.crt - Server certificate WITHOUT intermediate (causes the issue)
  • certs/server-bundle.crt - Server certificate WITH intermediate (the fix)

2. Start the Server with Incomplete Chain

node server.js

The server runs on https://localhost:8443 with an incomplete certificate chain.

3. Test the Issue

Test with cURL (will likely work)

chmod +x test-curl.sh
./test-curl.sh

Test with strict Node.js client (will fail - like Postman)

node test-client-strict.js

Expected output:

✗ FAILED!
Error: unable to verify the first certificate

This is the same error Postman shows!

Test with loose SSL verification (will work)

node test-client-loose.js

This simulates Postman with SSL verification disabled.

4. Compare with Fixed Server

Start the server with a complete certificate chain:

node server-fixed.js

Now test with strict SSL:

node test-client-strict.js --port 8444

This will ✅ succeed because the server sends the complete chain!

Why Does This Happen?

Certificate Chain Verification

SSL/TLS requires a chain of trust from the server certificate up to a trusted root CA:

[Server Certificate]
        ↓ signed by
[Intermediate CA] ← MISSING in misconfigured servers!
        ↓ signed by
[Root CA] ← Trusted by client

Different Tools, Different Behavior

  1. cURL: May use a more lenient verification process or cached intermediate certificates
  2. Browsers: Actively cache intermediate certificates from previous connections
  3. Node.js/Postman: Strictly require the server to send the complete chain

The Fix

Servers must send both:

  1. The server certificate
  2. All intermediate CA certificates (in order)

In Node.js:

// ❌ Wrong - only server cert
cert: fs.readFileSync('server.crt')

// ✅ Correct - server cert + intermediate(s)
cert: fs.readFileSync('server-bundle.crt')

Testing with Postman

  1. Import postman-collection.json (if you create one)
  2. Try request to https://localhost:8443 with SSL verification enabled → Should fail
  3. Disable SSL verification → Should work
  4. Try request to https://localhost:8444 with SSL verification enabled → Should work (fixed server)

Real-World Impact

This issue commonly occurs when:

  • Self-signed certificates are used without bundling intermediates
  • SSL certificates from providers like Namecheap, Let's Encrypt are installed incorrectly
  • Only the server certificate is installed, not the full chain
  • DevOps teams test with curl -k and don't notice the issue

Files

  • generate-certs.sh - Generates test certificates
  • server.js - Server with incomplete chain (reproduces issue)
  • server-fixed.js - Server with complete chain (fixed)
  • test-client-strict.js - Node.js client with strict SSL (like Postman)
  • test-client-loose.js - Node.js client with SSL verification disabled
  • test-curl.sh - Test with cURL

Common Error Messages

When the chain is incomplete, you'll see:

  • unable to verify the first certificate
  • UNABLE_TO_VERIFY_LEAF_SIGNATURE
  • UNABLE_TO_GET_ISSUER_CERT_LOCALLY

How to Fix in Production

Nginx

ssl_certificate /path/to/server-bundle.crt;  # Must include intermediates!
ssl_certificate_key /path/to/server.key;

Apache

SSLCertificateFile /path/to/server.crt
SSLCertificateKeyFile /path/to/server.key
SSLCertificateChainFile /path/to/intermediate.crt  # Important!

Node.js

const options = {
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server-bundle.crt') // Includes intermediates
};

Creating a Certificate Bundle

cat server.crt intermediate.crt > server-bundle.crt

Verifying Your Server's Chain

# Check what certificates a server sends
openssl s_client -connect localhost:8443 -showcerts

# Verify the certificate chain
openssl verify -CAfile root-ca.crt -untrusted intermediate-ca.crt server.crt

References

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published