You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[DO NOT MERGE] Connection encryption info – Slice A: public API surface - #4830
Slice A of the connection encryption information feature (related to #2353). It adds the public API surface only. SqlConnection.EncryptionInfo and SqlException.EncryptionInfo always return a non-null empty snapshot, with every field null. Nothing in the TdsParser or SNI layers populates the values yet.
New types: SqlConnectionEncryptionInfo, SqlConnectionEncryptionMode and SqlServerCertificateValidationErrors.
The SqlConnection.EncryptionInfo and SqlException.EncryptionInfo getters.
An internal constructor and an internal Empty instance, so later slices and tests can build populated snapshots.
Reference assembly, XML doc snippets, design note and unit tests.
Later slices (stacked on this branch)
B: capture the negotiated mode, TLS protocol and cipher suite, and attach the snapshot to SqlException when opening fails.
C: capture certificate details (the presented server certificate, validation errors and chain status) in Managed SNI, on both successful and failed attempts.
D: Native SNI interop for the certificate details and cipher suite.
Public API
namespaceMicrosoft.Data.SqlClient{publicsealedclassSqlConnectionEncryptionInfo{// No public constructor.publicSqlConnectionEncryptionMode?NegotiatedEncryptionMode{get;}publicSystem.Security.Authentication.SslProtocols?NegotiatedEncryptionProtocol{get;}publicint?NegotiatedCipherSuiteId{get;}// IANA TLS cipher suite id selected by the server, e.g. 0x1302 (needs API review)
#if NET[CLSCompliant(false)]publicSystem.Net.Security.TlsCipherSuite?NegotiatedCipherSuite{get;}// same value as an enum (.NET only)
#endif
publicSqlServerCertificateValidationErrors?ValidationErrors{get;}publicSystem.Security.Cryptography.X509Certificates.X509ChainStatusFlags?ChainStatus{get;}// Server-presented leaf certificate (public part only); new caller-owned instance per call, or null.publicSystem.Security.Cryptography.X509Certificates.X509Certificate2?GetServerCertificate();}publicenumSqlConnectionEncryptionMode{None=0,LoginOnly=1,Full=2,Strict=3}[Flags]publicenumSqlServerCertificateValidationErrors{None=0,ServerCertificateMissing=1,HostNameMismatch=2,ChainError=4,PinnedCertificateMismatch=8}publicsealedpartialclassSqlConnection{publicSqlConnectionEncryptionInfoEncryptionInfo{get;}// always non-null}publicsealedpartialclassSqlException{publicSqlConnectionEncryptionInfoEncryptionInfo{get;}// always non-null}}
Negotiated cipher suite (needs API review)
NegotiatedCipherSuiteId (int?, all targets) and the .NET-only NegotiatedCipherSuite (TlsCipherSuite?) are a proposal that hasn't been reviewed by the team yet.
Value: the IANA TLS cipher suite identifier the server selected in its ServerHello, for example 0x1302 for TLS_AES_256_GCM_SHA384. It is not a Windows ALG_ID. Only the selected suite is reported: not the client's offered list, CAPI2 chain detail, or key-exchange/PQC group information.
Why two members:NegotiatedCipherSuiteId has the same CLS-compliant signature on every target, so libraries compiled against the netstandard2.0 reference bind correctly at run time. TlsCipherSuite exists only on .NET and its ushort backing isn't CLS-compliant, so the strongly typed property is .NET-only and marked [CLSCompliant(false)]. Unknown IANA ids are preserved. The internal constructor rejects values outside 0 to 0xFFFF.
null means the driver couldn't obtain the value (no TLS, not captured, older SNI, or a failure before it was known). It doesn't mean the server selected no suite. On failure, an observed value is kept, like the protocol.
Capture is added in Slices B (managed) and D (native SNI). In this slice the value is always null.
Notes for reviewers
No behaviour changes: SqlException serialization, Message and ToString() are unchanged. The snapshot is not serialized, so a deserialized exception returns an empty snapshot.
Server certificate is a method: after the threat model review, the certificate hash was replaced with the full server-presented leaf certificate. GetServerCertificate() parses a private DER copy and returns a new X509Certificate2 on every call, which the caller owns and should dispose; it returns null when no certificate was captured or the bytes can't be parsed. It's a method rather than a property because each call allocates a disposable object. The certificate is for diagnostics only, has no private key, and isn't a trust decision. The design note explains the rationale.
SqlConnectionEncryptionInfoTests after the certificate change (f6038d1):
net10.0: 56 passed, 0 failed
net462: 56 passed, 0 failed
The new tests cover:
empty-snapshot invariants
getters on new, closed and disposed connections, and on every SqlException factory and InternalClone
serialization round trips
Message, ToString() and serialized payloads unchanged, with no certificate data in them
populated round trips
null kept distinct from zero values (None, NoError)
GetServerCertificate(): new instance per call, round trip (thumbprint and raw data), no private key, defensive copying, clone isolation, unparsable bytes and null/empty input return null, IsEmpty
flags combinations
cipher suite: per-target members via reflection, null by default, IANA values (including unknown/GREASE ids, 0 and 0xFFFF) round-trip, enum mapping on .NET, out-of-range values rejected, kept by InternalClone and absent from ToString()
enum numeric contracts
Checklist
Tests added or updated
Public API changes documented (doc/snippets/Microsoft.Data.SqlClient/*.xml)
Verified against customer repro (if applicable): not applicable to this API-only slice
Ensure no breaking changes introduced (additive API only)
✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.20%. Comparing base (7b1184b) to head (9701ce8). ⚠️ Report is 9 commits behind head on main.
ReadOnlyMemory<byte> does not protect the backing array: callers can recover writable storage with MemoryMarshal.TryGetArray/AsMemory and alter this private array. That changes later reads from the supposedly immutable snapshot and makes the current ReturnedCopy test pass only because it calls ToArray() first. Return a fresh defensive copy from the getter (and update the test to mutate the returned memory through MemoryMarshal) so external mutation cannot alter the snapshot.
Document the test helper with summary, parameters, and return
Add the required XML documentation for this test helper. Repository policy requires test helpers to have a summary plus applicable parameter and return documentation (.github/instructions/testing.instructions.md:180-193).
priyankatiwari08
changed the title
[Draft] Connection encryption info – Slice A: public API surface
[DO NOT MERGE] [Draft] Connection encryption info – Slice A: public API surface
Oct 19, 2026
mdaigle
linked an issue
Oct 20, 2026
that may be
closed
by this pull request
Adds SqlConnectionEncryptionInfo, SqlConnectionEncryptionMode, and
SqlServerCertificateValidationErrors, plus SqlConnection.EncryptionInfo and
SqlException.EncryptionInfo getters. In this slice both getters always return
an empty snapshot; no TdsParser/SNI plumbing yet.
Related to #2353.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…gn note
Replace PresentedServerCertificateThumbprint (string?) with
PresentedServerCertificateHash256 (ReadOnlyMemory<byte>?), per the API
agreed with the SSMS team. The internal constructor validates the hash is
32 bytes and keeps a private copy. Add doc/design-notes/2353 for reviewers.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds int? NegotiatedCipherSuite (IANA TLS cipher suite identifier selected by the server) to the public snapshot, ref assembly, XML docs, design note and unit tests. Proposal pending API review.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
priyankatiwari08
changed the title
[DO NOT MERGE] [Draft] Connection encryption info – Slice A: public API surface
[DO NOT MERGE] Connection encryption info – Slice A: public API surface
Oct 27, 2026
…ite? on .NET (review feedback)
- Add NegotiatedCipherSuiteId (int?, CLS-compliant) on every target framework.
It returns the IANA identifier unchanged, including unnamed/GREASE values.
- On .NET only, NegotiatedCipherSuite exposes the same value as
System.Net.Security.TlsCipherSuite? and is marked [CLSCompliant(false)].
- No NegotiatedCipherSuite member on net462/netstandard2.0, so the only shared
member has an identical signature across the netstandard2.0 ref and the
net10.0 implementation.
- The internal constructor still takes int? (0..0xFFFF). The internal
NegotiatedCipherSuiteValue is removed.
- Update ref, XML docs, design note and unit tests.
Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The PR description and API example still conflict with the implemented cipher-suite properties and must document both properties and their target-framework availability.
… (threat model review)
Remove PresentedServerCertificateHash256 and expose the server-presented leaf
certificate via X509Certificate2? GetServerCertificate(). Each call returns a
new caller-owned instance parsed from a private DER copy, or null when no
certificate was captured or the bytes cannot be parsed. The internal ctor now
takes byte[]? presentedServerCertificate, and PresentedServerCertificateRawData
exposes the captured bytes internally. Update ref, XML docs, design note and
unit tests.
Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The permanent public encryption-diagnostics contract requires human API-design sign-off on the cross-framework cipher-suite identifier and .NET-only typed member before finalization.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Slice A of the connection encryption information feature (related to #2353). It adds the public API surface only.
SqlConnection.EncryptionInfoandSqlException.EncryptionInfoalways return a non-null empty snapshot, with every fieldnull. Nothing in the TdsParser or SNI layers populates the values yet.For background, scenarios and the intended behaviour, see the design note added in this PR:
doc/design-notes/2353-connection-encryption-info.md.Scope
In this PR (Slice A)
SqlConnectionEncryptionInfo,SqlConnectionEncryptionModeandSqlServerCertificateValidationErrors.SqlConnection.EncryptionInfoandSqlException.EncryptionInfogetters.Emptyinstance, so later slices and tests can build populated snapshots.Later slices (stacked on this branch)
SqlExceptionwhen opening fails.Public API
Negotiated cipher suite (needs API review)
NegotiatedCipherSuiteId(int?, all targets) and the .NET-onlyNegotiatedCipherSuite(TlsCipherSuite?) are a proposal that hasn't been reviewed by the team yet.0x1302forTLS_AES_256_GCM_SHA384. It is not a WindowsALG_ID. Only the selected suite is reported: not the client's offered list, CAPI2 chain detail, or key-exchange/PQC group information.NegotiatedCipherSuiteIdhas the same CLS-compliant signature on every target, so libraries compiled against the netstandard2.0 reference bind correctly at run time.TlsCipherSuiteexists only on .NET and itsushortbacking isn't CLS-compliant, so the strongly typed property is .NET-only and marked[CLSCompliant(false)]. Unknown IANA ids are preserved. The internal constructor rejects values outside 0 to 0xFFFF.nullmeans the driver couldn't obtain the value (no TLS, not captured, older SNI, or a failure before it was known). It doesn't mean the server selected no suite. On failure, an observed value is kept, like the protocol.null.Notes for reviewers
SqlExceptionserialization,MessageandToString()are unchanged. The snapshot is not serialized, so a deserialized exception returns an empty snapshot.GetServerCertificate()parses a private DER copy and returns a newX509Certificate2on every call, which the caller owns and should dispose; it returnsnullwhen no certificate was captured or the bytes can't be parsed. It's a method rather than a property because each call allocates a disposable object. The certificate is for diagnostics only, has no private key, and isn't a trust decision. The design note explains the rationale.Test results (Windows, Debug)
net462;net10.0), ref and not-supported (GenAPI) builds: 0 warnings, 0 errors.SqlConnectionEncryptionInfoTestsafter the certificate change (f6038d1):The new tests cover:
SqlExceptionfactory andInternalCloneMessage,ToString()and serialized payloads unchanged, with no certificate data in themnullkept distinct from zero values (None,NoError)GetServerCertificate(): new instance per call, round trip (thumbprint and raw data), no private key, defensive copying, clone isolation, unparsable bytes and null/empty input returnnull,IsEmptyInternalCloneand absent fromToString()Checklist
doc/snippets/Microsoft.Data.SqlClient/*.xml)