@@ -86,6 +86,110 @@ function Get-DbaNetworkEncryption {
8686 [switch ]$EnableException
8787 )
8888 begin {
89+ # SQL Server wraps TLS handshake messages inside TDS packets (type 0x12) during negotiation.
90+ # This helper stream transparently adds/strips TDS framing so that SslStream can perform
91+ # the TLS handshake correctly over the SQL Server pre-login channel.
92+ if (-not (' DbaTools.TdsWrappingStream' -as [type ])) {
93+ Add-Type - TypeDefinition @"
94+ using System;
95+ using System.IO;
96+
97+ namespace DbaTools {
98+ public class TdsWrappingStream : Stream {
99+ private Stream _inner;
100+ private byte _packetType;
101+ private byte _packetId;
102+ private byte[] _readBuffer;
103+ private int _readPos;
104+ private int _readCount;
105+
106+ public TdsWrappingStream(Stream inner, byte packetType) {
107+ _inner = inner;
108+ _packetType = packetType;
109+ _packetId = 1;
110+ _readBuffer = null;
111+ _readPos = 0;
112+ _readCount = 0;
113+ }
114+
115+ public override bool CanRead { get { return true; } }
116+ public override bool CanWrite { get { return true; } }
117+ public override bool CanSeek { get { return false; } }
118+ public override long Length { get { throw new NotSupportedException(); } }
119+ public override long Position {
120+ get { throw new NotSupportedException(); }
121+ set { throw new NotSupportedException(); }
122+ }
123+
124+ public override void Flush() { _inner.Flush(); }
125+
126+ // Wrap outgoing data in TDS packet(s) before sending to SQL Server.
127+ public override void Write(byte[] buffer, int offset, int count) {
128+ int maxPayload = 32760;
129+ int remaining = count;
130+ int srcOffset = offset;
131+ while (remaining > 0) {
132+ int chunkSize = remaining < maxPayload ? remaining : maxPayload;
133+ bool isLast = (remaining - chunkSize) == 0;
134+ int packetLen = chunkSize + 8;
135+ byte[] header = new byte[] {
136+ _packetType,
137+ isLast ? (byte)0x01 : (byte)0x00,
138+ (byte)(packetLen >> 8),
139+ (byte)(packetLen & 0xFF),
140+ 0x00, 0x00,
141+ _packetId++,
142+ 0x00
143+ };
144+ _inner.Write(header, 0, 8);
145+ _inner.Write(buffer, srcOffset, chunkSize);
146+ srcOffset += chunkSize;
147+ remaining -= chunkSize;
148+ }
149+ }
150+
151+ // Strip TDS packet framing from incoming data before delivering to SslStream.
152+ public override int Read(byte[] buffer, int offset, int count) {
153+ // Return buffered payload from the previous TDS packet first.
154+ if (_readBuffer != null && _readPos < _readCount) {
155+ int available = _readCount - _readPos;
156+ int toCopy = available < count ? available : count;
157+ Array.Copy(_readBuffer, _readPos, buffer, offset, toCopy);
158+ _readPos += toCopy;
159+ return toCopy;
160+ }
161+ // Read the 8-byte TDS header of the next packet.
162+ byte[] header = new byte[8];
163+ int headerRead = 0;
164+ while (headerRead < 8) {
165+ int n = _inner.Read(header, headerRead, 8 - headerRead);
166+ if (n == 0) return 0;
167+ headerRead += n;
168+ }
169+ int payloadLen = ((header[2] << 8) | header[3]) - 8;
170+ if (payloadLen <= 0) return 0;
171+ // Read the full payload.
172+ _readBuffer = new byte[payloadLen];
173+ _readCount = 0;
174+ while (_readCount < payloadLen) {
175+ int n = _inner.Read(_readBuffer, _readCount, payloadLen - _readCount);
176+ if (n == 0) break;
177+ _readCount += n;
178+ }
179+ _readPos = 0;
180+ int toCopyNow = _readCount < count ? _readCount : count;
181+ Array.Copy(_readBuffer, 0, buffer, offset, toCopyNow);
182+ _readPos = toCopyNow;
183+ return toCopyNow;
184+ }
185+
186+ public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
187+ public override void SetLength(long value) { throw new NotSupportedException(); }
188+ }
189+ }
190+ "@
191+ }
192+
89193 function Get-SqlBrowserPort {
90194 param (
91195 [string ]$ComputerName ,
@@ -164,8 +268,8 @@ function Get-DbaNetworkEncryption {
164268
165269 $networkStream = $tcpClient.GetStream ()
166270
167- # We need to send a SQL Server pre-login packet before doing TLS,
168- # because SQL Server uses STARTTLS-style negotiation.
271+ # Send a SQL Server pre-login packet requesting ENCRYPT_ON (0x01).
272+ # SQL Server uses STARTTLS-style negotiation: TLS only starts after this exchange .
169273 # Pre-login packet layout (26 bytes total):
170274 # TDS header (8 bytes): type=0x12 (PRE_LOGIN), status=0x01 (EOM), length=0x001A (26)
171275 # Payload option headers (11 bytes):
@@ -195,7 +299,36 @@ function Get-DbaNetworkEncryption {
195299 throw " Invalid pre-login response from ${ComputerName} :${Port} "
196300 }
197301
198- # Now wrap in SSL stream and do TLS handshake
302+ # Parse the ENCRYPTION option from the server's pre-login response.
303+ # Option offsets in the payload are relative to the start of the payload (byte 8).
304+ $payloadStart = 8
305+ $optionOffset = $payloadStart
306+ $serverEncryption = $null
307+ while ($optionOffset -lt ($bytesRead - 4 )) {
308+ $optionType = $responseBuffer [$optionOffset ]
309+ if ($optionType -eq 0xFF ) { break } # TERMINATOR
310+ $dataOffset = ($responseBuffer [$optionOffset + 1 ] -shl 8 ) -bor $responseBuffer [$optionOffset + 2 ]
311+ $dataLength = ($responseBuffer [$optionOffset + 3 ] -shl 8 ) -bor $responseBuffer [$optionOffset + 4 ]
312+ if ($optionType -eq 0x01 ) {
313+ # ENCRYPTION option
314+ $absoluteDataOffset = $payloadStart + $dataOffset
315+ if ($absoluteDataOffset -lt $bytesRead ) {
316+ $serverEncryption = $responseBuffer [$absoluteDataOffset ]
317+ }
318+ break
319+ }
320+ $optionOffset += 5
321+ }
322+
323+ if ($serverEncryption -eq 0x02 ) {
324+ # ENCRYPT_NOT_SUP - server does not support TLS at all
325+ throw " Server does not support TLS encryption - no certificate is presented"
326+ }
327+
328+ # SQL Server wraps TLS handshake messages in TDS packets (type 0x12).
329+ # TdsWrappingStream adds/strips that framing so SslStream negotiates correctly.
330+ $tdsStream = New-Object DbaTools.TdsWrappingStream($networkStream , [byte ]0x12 )
331+
199332 # The server certificate is captured via the validation callback
200333 $script :capturedCertificate = $null
201334
@@ -206,7 +339,7 @@ function Get-DbaNetworkEncryption {
206339 }
207340
208341 $sslStream = New-Object System.Net.Security.SslStream(
209- $networkStream ,
342+ $tdsStream ,
210343 $false ,
211344 $certValidationCallback
212345 )
0 commit comments