Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/advanced-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ fetch --inspect-tls example.com

The default output uses the structured diagnostic view. It shows separate connection and certificate sections with the remote address, negotiated TLS, verification status, leaf certificate, issuer, exact validity window, serial number, public-key description, signature algorithm, SHA-256 fingerprint, server and verified chains, all parsed SAN types, OCSP status and timestamps, SCT count, SNI, resolver provenance, and ECH details. The `-v` flag has no effect in TLS inspection mode. Use `-vv` for AIA, CRL, policy, and other niche X.509 details. OCSP staple status checks the matching certificate and response signature, but not responder authorization or freshness. QUIC reports `cipher suite unavailable` only when the transport does not expose it.

Inspection completes the handshake even when certificate verification fails. It returns a nonzero status for a verification failure unless `--insecure` is explicit; `--insecure` reports the failure as ignored and returns success. Expiry is color-coded in the certificate view: red if expired or less than 7 days remaining, yellow if less than 30 days, green otherwise. The inspection result is written to stdout. Warnings and errors are written to stderr, so the result can be redirected or piped without diagnostic output.
Inspection completes the handshake even when certificate verification fails. It returns a nonzero status for a verification failure unless `--insecure` is explicit; `--insecure` reports the failure as ignored and returns success. Certificate validity is color-coded in the certificate view: red if the certificate is not yet valid, expired, or has less than 7 days remaining; yellow if it has less than 30 days remaining; green otherwise. The inspection result is written to stdout. Warnings and errors are written to stderr, so the result can be redirected or piped without diagnostic output.

HTTP-only flags (e.g. `--data`, `--timing`, `--grpc`) are ignored with a warning when used with `--inspect-tls`.

Expand Down
15 changes: 15 additions & 0 deletions internal/tlsinspect/tlsinspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,9 @@ func renderVerboseVerification(p *core.Printer, result *verificationResult, inse

func certificateValidityStatus(cert *x509.Certificate) string {
now := tlsInspectNow()
if now.Before(cert.NotBefore) {
return "not yet valid"
}
if now.After(cert.NotAfter) {
return "expired"
}
Expand Down Expand Up @@ -922,6 +925,18 @@ func certDisplayName(cert *x509.Certificate) string {

func certExpiryInfo(cert *x509.Certificate) (string, core.Sequence) {
now := tlsInspectNow()
if now.Before(cert.NotBefore) {
untilValid := cert.NotBefore.Sub(now)
days := int(untilValid.Hours() / 24)
switch days {
case 0:
return "valid in <1 day", core.Red
case 1:
return "valid in 1 day", core.Red
default:
return fmt.Sprintf("valid in %d days", days), core.Red
}
}
if now.After(cert.NotAfter) {
return "expired", core.Red
}
Expand Down
61 changes: 60 additions & 1 deletion internal/tlsinspect/tlsinspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,10 +397,32 @@ func TestCertExpiryInfo(t *testing.T) {

tests := []struct {
name string
notBefore time.Time
notAfter time.Time
wantText string
wantColor core.Sequence
}{
{
name: "not valid for less than 1 day",
notBefore: fixedNow.Add(12 * time.Hour),
notAfter: fixedNow.Add(30 * 24 * time.Hour),
wantText: "valid in <1 day",
wantColor: core.Red,
},
{
name: "not valid for 1 day",
notBefore: fixedNow.Add(36 * time.Hour),
notAfter: fixedNow.Add(30 * 24 * time.Hour),
wantText: "valid in 1 day",
wantColor: core.Red,
},
{
name: "not valid for multiple days",
notBefore: fixedNow.Add(3 * 24 * time.Hour),
notAfter: fixedNow.Add(30 * 24 * time.Hour),
wantText: "valid in 3 days",
wantColor: core.Red,
},
{
name: "expired",
notAfter: fixedNow.Add(-24 * time.Hour),
Expand Down Expand Up @@ -459,7 +481,7 @@ func TestCertExpiryInfo(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cert := &x509.Certificate{NotAfter: tt.notAfter}
cert := &x509.Certificate{NotBefore: tt.notBefore, NotAfter: tt.notAfter}
gotText, gotColor := certExpiryInfo(cert)
if gotText != tt.wantText {
t.Errorf("certExpiryInfo() text = %q, want %q", gotText, tt.wantText)
Expand All @@ -471,6 +493,43 @@ func TestCertExpiryInfo(t *testing.T) {
}
}

func TestCertificateValidityStatus(t *testing.T) {
fixedNow := time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC)
origNow := tlsInspectNow
tlsInspectNow = func() time.Time { return fixedNow }
t.Cleanup(func() { tlsInspectNow = origNow })

tests := []struct {
name string
cert *x509.Certificate
want string
}{
{
name: "not yet valid",
cert: &x509.Certificate{NotBefore: fixedNow.Add(time.Hour), NotAfter: fixedNow.Add(24 * time.Hour)},
want: "not yet valid",
},
{
name: "expired",
cert: &x509.Certificate{NotBefore: fixedNow.Add(-48 * time.Hour), NotAfter: fixedNow.Add(-time.Hour)},
want: "expired",
},
{
name: "valid",
cert: &x509.Certificate{NotBefore: fixedNow.Add(-time.Hour), NotAfter: fixedNow.Add(36 * time.Hour)},
want: "1 day remaining",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := certificateValidityStatus(tt.cert); got != tt.want {
t.Fatalf("certificateValidityStatus() = %q, want %q", got, tt.want)
}
})
}
}

func TestRenderCertificateChain(t *testing.T) {
fixedNow := time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC)
origNow := tlsInspectNow
Expand Down
Loading