Make driver fail certificate validation for untrusted chains (with opt-out for backward compatibility)#665
Make driver fail certificate validation for untrusted chains (with opt-out for backward compatibility)#665
Conversation
|
@copilot please complete the work on the PR |
- Add custom VerifyPeerCertificate callback to ensure full chain validation - Verify that certificate chains terminate at self-signed root certificates - Add comprehensive unit tests for certificate validation - Update .gitignore to exclude test certificate serial files Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com>
Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com>
Completed the implementation in commit 897cc7a. The driver now performs strict TLS certificate chain validation by:
All tests pass including comprehensive unit tests for certificate validation. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
| @@ -1,7 +1,5 @@ | |||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||
| github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= | |||
There was a problem hiding this comment.
@copilot - how is this change relevant to the issue and PR?
There was a problem hiding this comment.
You're correct - those dependency changes were not relevant to the PR. They were automatically updated when Go downloaded dependencies during testing. I've reverted them in commit 33d6855 to keep the PR focused only on the TLS certificate validation changes.
|
@copilot - since this is a behavior change, we need a configuration option NOT to use this change, which will keep the old path. It should be off by default (which means - enable the new behavior by default) with a deprecation warning that it will be removed in future driver versions. |
Reverted go.sum, lz4/go.sum, tests/bench/go.mod, and tests/bench/go.sum to their original state as these dependency updates were unrelated to the TLS certificate validation feature. Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com>
- Added DisableStrictCertificateValidation field to SslOptions - Default is false (strict validation enabled by default) - Emits deprecation warning when the option is used - Updated setupTLSConfig to accept logger and respect the new option - Added test case for the new configuration option - All existing tests pass Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com>
The deprecation warning now shows whenever DisableStrictCertificateValidation is set to true, regardless of other TLS configuration settings. This ensures users are always informed when using the deprecated option. Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com>
Implemented in commits 4a9af3e and e09c4d6. Added
This provides backward compatibility while encouraging users to migrate to properly configured certificate chains. |
| return fmt.Errorf("certificate verification failed: %v", err) | ||
| } | ||
|
|
||
| // Ensure that the chain terminates at a trusted root certificate |
| // Verify that each chain ends with a certificate that's in our RootCAs pool | ||
| // This ensures we don't accept chains that terminate at an intermediate cert |
| // Verify this root is self-signed (characteristic of root CAs) | ||
| if err := rootCert.CheckSignatureFrom(rootCert); err != nil { | ||
| return fmt.Errorf("certificate chain does not terminate at a self-signed root: %v", err) | ||
| } |
There was a problem hiding this comment.
This does not make any sense, it should check all the certificates in the chain if they signed with root certificate
There was a problem hiding this comment.
Simplified in commit 5b4f2be. Removed the self-signed check since cert.Verify() already validates the entire chain properly. The function now relies on cert.Verify() which validates that all certificates in the chain are properly signed, ultimately by a certificate in the RootCAs pool.
|
@dkropachev - I did not review it yet fully - don't waste your time on draft. |
|
@copilot - please address all feedback. |
- Removed redundant comments as requested - Removed self-signed root check - cert.Verify() already validates the chain properly - Updated test expectations to match simplified implementation - Updated function documentation to better describe what it does The strict validation now simply ensures cert.Verify() is called with properly configured Roots and Intermediates pools, which provides proper chain validation. Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com>
| // Verify this root is self-signed (characteristic of root CAs) | ||
| if err := rootCert.CheckSignatureFrom(rootCert); err != nil { | ||
| return fmt.Errorf("certificate chain does not terminate at a self-signed root: %v", err) | ||
| } |
There was a problem hiding this comment.
This one does not make any sense, we need to to check if every certificate in the chain is trusted by root certificate.
There was a problem hiding this comment.
Implemented in commit 8029281. Added explicit verification that every certificate in the chain is signed by its parent, all the way to a self-signed root certificate. This ensures intermediate CAs cannot be trusted as roots.
|
|
||
| _, err = cert.Verify(opts) | ||
| if err != nil { | ||
| return fmt.Errorf("certificate verification failed: %v", err) |
There was a problem hiding this comment.
@copilot - is there any information we can add here, which certificate failed verification, for example?
There was a problem hiding this comment.
Enhanced in commit 8029281. Error messages now include the certificate subject and issuer information, e.g., certificate verification failed for subject="CN=example.com,O=Example" issuer="CN=Intermediate CA"
…re verification - Added detailed error messages showing subject and issuer for failed certificates - Implemented explicit chain signature verification to ensure every certificate is signed by its parent all the way to a self-signed root - This prevents accepting intermediate CAs as trusted roots (security issue) - Updated test to expect rejection when intermediate CA is in root pool - All tests passing, linter clean Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com>
| } | ||
|
|
||
| // No valid chain found where all certificates are properly signed | ||
| return fmt.Errorf("no valid certificate chain found with proper signatures for subject=%q", cert.Subject.String()) |
There was a problem hiding this comment.
@copilot - I wonder why this line is not used instead of the break in line 712?
There was a problem hiding this comment.
The break on line 712 exits the inner loop that checks signatures within a single chain. The chainValid flag is then checked at line 717 - if false, we skip the self-signed root check and the outer loop continues to the next chain. Using continue would require being in the outer loop context. The current logic works correctly: when a signature fails, we mark the chain invalid, break from signature checking, and implicitly continue to the next chain.
Progress Checklist
Summary
Successfully implemented strict TLS certificate chain validation to address the security concern that Go's default behavior is too lenient compared to OpenSSL. The implementation:
Added
strictVerifyPeerCertificatefunction - Custom verification callback that:cert.Verify()with properly configured Roots and Intermediates poolsModified
setupTLSConfig- Automatically applies strict validation whenInsecureSkipVerifyis falseAdded
DisableStrictCertificateValidationoption - For backward compatibility:Comprehensive testing - Added tests that validate:
All checks pass:
Enhanced Security Features
The implementation provides defense-in-depth validation:
cert.Verify()validates the basic certificate chain structureThis approach directly addresses the original security concern: preventing acceptance of certificate chains where only an intermediate (not the root) is trusted.
Usage Example
To use the new strict validation (default behavior):
To temporarily disable strict validation for backward compatibility (not recommended):
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.