Skip to content

fix: add actionable guidance for TLS certificate failures - #356

Open
David Levy (dlevy-msft-sql) wants to merge 25 commits into
microsoft:mainfrom
dlevy-msft-sql:fix/tls-handshake-error-guidance
Open

David Levy (dlevy-msft-sql) wants to merge 25 commits into
microsoft:mainfrom
dlevy-msft-sql:fix/tls-handshake-error-guidance

Conversation

@dlevy-msft-sql

@dlevy-msft-sql David Levy (dlevy-msft-sql) commented Apr 17, 2026 •

Copy link
Copy Markdown

Problem

TLS handshake failures caused by invalid certificates or obsolete TLS configuration provide little remediation guidance:

Root cause

The driver returned the underlying TLS error without identifying the server-side certificate or TLS configuration change needed to resolve it.

Solution

Recognize these certificate and TLS failures and put permanent server-side remediation first. The errors recommend replacing invalid certificates or updating obsolete TLS configuration. Where supported, they also identify GODEBUG settings as temporary compatibility options.

Changes

File Change
tds.go Add wrapTLSError, apply it at both TLS handshake sites, and preserve wrapped errors with %w
tds_unit_test.go Cover each recognized failure, fallback behavior, error unwrapping, and both handshake paths

Testing

  • go build ./...
  • go test -count=1 -run 'TestWrapTLSError|TestGetTLSConnHandshakeError|TestConnectNonStrictTLSHandshakeError' .
  • CI covers the full SQL Server and Go version matrix

Related issues

Closes #302
Closes #217

@codecov-commenter

Codecov Comments Bot (codecov-commenter) commented Apr 17, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.31%. Comparing base (60674a5) to head (abea722).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main     #356       +/-   ##
===========================================
+ Coverage   81.66%   96.31%   +14.65%     
===========================================
  Files          34       91       +57     
  Lines        7056    75317    +68261     
===========================================
+ Hits         5762    72539    +66777     
- Misses       1027     2758     +1731     
+ Partials      267       20      -247     
Flag Coverage Δ
unittests 96.31% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
tds.go 81.31% <100.00%> (+6.89%) ⬆️

... and 74 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves the driver’s TLS handshake failure diagnostics by wrapping handshake errors with actionable guidance (including relevant GODEBUG workarounds) for known Go crypto/tls policy changes, helping users self-remediate certificate-related failures.

Changes:

  • Add wrapTLSError() in tds.go to recognize specific TLS/cert failure strings and append remediation guidance.
  • Apply the wrapper at both TLS handshake sites (getTLSConn() and the standard encryption path in connect()), ensuring proper error wrapping (%w).
  • Add unit tests covering the known patterns and default behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
tds.go Introduces wrapTLSError() and uses it at both TLS handshake call sites to enrich errors with guidance.
tds_unit_test.go Adds unit tests validating wrapping behavior, message contents, and errors.Is unwrapping.

Comment thread tds.go Outdated
Comment thread tds.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

Comment thread tds_unit_test.go Outdated
Comment thread tds_unit_test.go
Comment thread tds.go Outdated
Comment thread tds_unit_test.go
Comment thread tds_unit_test.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread tds.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

Comment thread tds.go Outdated
Comment thread tds.go Outdated
Comment thread tds_unit_test.go Outdated
Comment thread tds_unit_test.go Outdated
Comment thread tds_unit_test.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

silverwind pushed a commit to go-gitea/gitea that referenced this pull request May 27, 2026
The following TLS handshake error is fixed by newer versions of mssql
(refer to
microsoft/mssql-docker#895 (comment))

```
TLS Handshake failed: tls: failed to parse certificate from server: x509: negative serial number
```

Based on
microsoft/go-sqlcmd#755 (comment),
newer versions of mssql don't have this problem. And there're changes
going to mssql driver side to make this error more explicit
microsoft/go-mssqldb#356.

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
z (zeekay) pushed a commit to hanzoai/forge that referenced this pull request Jul 26, 2026
The following TLS handshake error is fixed by newer versions of mssql
(refer to
microsoft/mssql-docker#895 (comment))

```
TLS Handshake failed: tls: failed to parse certificate from server: x509: negative serial number
```

Based on
microsoft/go-sqlcmd#755 (comment),
newer versions of mssql don't have this problem. And there're changes
going to mssql driver side to make this error more explicit
microsoft/go-mssqldb#356.

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
Copilot AI review requested due to automatic review settings September 11, 2026 06:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

SHA-1 matching misses Go’s hyphenated SHA-1 format, leaving the intended guidance unreachable.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@dlevy-msft-sql

David Levy (dlevy-msft-sql) commented Sep 11, 2026 •

Copy link
Copy Markdown
Author

Refuted: Go 1.25's crypto/x509.InsecureAlgorithmError formats SHA-1 algorithms as SHA1-RSA and ECDSA-SHA1, as asserted in the standard library's X.509 tests. Both lowercase to strings containing sha1, so the current matcher reaches this branch for both standard forms. The unit test constructs x509.InsecureAlgorithmError(x509.SHA1WithRSA), which exercises the actual formatter rather than synthetic text.

Copilot AI review requested due to automatic review settings September 23, 2026 02:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

SHA-1 errors are not recognized correctly, causing the related test and remediation guidance to fail.

Review effort: Lite
Findings: None

Copilot AI review requested due to automatic review settings September 23, 2026 02:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The SHA-1 guidance branch is unreachable for the reported standard-library error format.

Review effort: Lite
Findings: None

Copilot AI review requested due to automatic review settings September 23, 2026 05:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The changes are covered by tests and introduce no unresolved blocking issues.

Review effort: Lite
Findings: None

Copilot AI review requested due to automatic review settings September 23, 2026 07:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

SHA-1 matching misses Go’s SHA-1 spelling, so the intended guidance is not emitted.

Review effort: Lite
Findings: None

Copilot AI review requested due to automatic review settings September 23, 2026 11:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

SHA-1 matching misses Go’s SHA-1 spelling, causing the expected guidance test to fail.

Review effort: Lite
Findings: None

@dlevy-msft-sql

Copy link
Copy Markdown
Author

The benchmarks check fails on ConvertAssign_BoolToBool (+23.60%, p=0.000). I investigated it instead of re-running past it. It is a code-layout artifact, not a performance change from this PR.

Evidence

wrapTLSError is a new package-level function in tds.go, called from exactly two places, both TLS handshake error paths. convertAssign lives in convert.go and has no call path to anything this PR touches. The diff is 253 lines across tds.go and tds_unit_test.go.

I built the test binary from origin/main and from this branch, then disassembled convertAssign in both:

main PR
instruction count 1533 1533
symbol address 0x14047bac0 0x14047bfc0
differing instruction lines 205 of 1533

Same instruction count, same sequence, same mnemonics. Every one of the 205 differing lines is a relocation operand: 80 LEAQ, 78 CALL, 27 MOVQ, 18 CMPL, 1 MOVZX. Those are call displacements and PC-relative addresses of symbols that moved. Adding 37 lines to tds.go pushes everything after it by 1280 bytes. The compiled body of convertAssign is unchanged.

Local A/B on those same two binaries, -cpu=4 -benchtime=1s -count=10:

ns/op
main 19.5
PR 19.1

No regression locally. The PR side is marginally faster.

Why CI sees it

The job builds the baseline from a separate origin/main worktree, so the two sides are different binaries with different symbol placement. ConvertAssign_BoolToBool is a 13ns operation carrying one allocation, small enough that x86 code alignment moves the number by 20% or more. Both failing runs show ±0-1% variance within each side and a stable gap between sides, which is the shape of a deterministic layout difference rather than runner drift. Everything else in the comparison is ~, geomean -0.35%.

Runs: job 107153657614 (+22.32%) and job 107347674431 (+23.60%). Same benchmark, same magnitude, so re-running again will not clear it.

What I did not change

I did not widen the 15% threshold and did not add ConvertAssign_BoolToBool to the exclusion list. Either would make the check pass without making the measurement any more trustworthy. That decision is yours. The gate is behaving as written, and changing pr-validation.yml is outside this PR's scope.

Copilot AI review requested due to automatic review settings September 24, 2026 06:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Fix SHA-1 error matching and add strict-path coverage for recognized TLS failures.

Review effort: Lite
Findings: None

@dlevy-msft-sql

Copy link
Copy Markdown
Author

Third reproduction, now on head abea722 (run job 107514971808):

ConvertAssign_BoolToBool-4    13.77n ± 1%   16.79n ± 0%  +22.01% (p=0.000 n=10)
geomean                       265.9n        264.6n        -0.50%

Same benchmark, same magnitude (+22.32%, +23.60%, +22.01% across three runs on three different heads), everything else ~, geomean still negative. This confirms the prediction in my earlier comment: re-running will not clear it, because the cause is deterministic code placement, not runner drift.

Nothing in this PR touches convertAssign or anything it calls. I have not changed the benchmark, the gate threshold, or the exclusion list. The call is yours on whether to widen the gate, exclude this benchmark, or override.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Go 1.25 "TLS Handshake failed: cannot read handshake packet: EOF" Go 1.23: Unable to connect to SQL Server 2022 docker image with TLS error

4 participants