diff --git a/samples/manage/get-tds-certificate/README.md b/samples/manage/get-tds-certificate/README.md new file mode 100644 index 00000000..b35beab3 --- /dev/null +++ b/samples/manage/get-tds-certificate/README.md @@ -0,0 +1,71 @@ +# Get TDS certificate from Azure SQL using PowerShell + +Script that downloads TDS certificate with public key only from Azure SQL + +### Contents + +[About this sample](#about-this-sample)
+[Before you begin](#before-you-begin)
+[Run this sample](#run-this-sample)
+[Sample details](#sample-details)
+[Disclaimers](#disclaimers)
+[Related links](#related-links)
+ + + +## About this sample + +- **Applies to:** Azure SQL +- **Key features:** Database, Managed Instance +- **Workload:** n/a +- **Programming Language:** PowerShell +- **Authors:** Srdan Bozovic +- **Update history:** n/a + + + +## Before you begin + +To run this sample, you need the following prerequisites. + +**Software prerequisites:** + +1. PowerShell 5.0 or higher installed + + + +## Run this sample + +Run the script below from either Windows or Azure Cloud Shell + +```powershell + +$scriptUrlBase = 'https://raw.githubusercontent.com/Microsoft/sql-server-samples/master/samples/manage/get-tds-certificate' + +$parameters = @{ + hostName = '' + port = '' + publicCertificateFile = '' + } + +Invoke-Command -ScriptBlock ([Scriptblock]::Create((iwr ($scriptUrlBase+'/getTDSCertificate.ps1?t='+ [DateTime]::Now.Ticks)).Content)) -ArgumentList $parameters + +``` + + + +## Sample details + +This sample shows how to retreive Azure SQL TDS certificate and save it to DER encoded X509 binary. + + + +## Disclaimers +The scripts and this guide are copyright Microsoft Corporations and are provided as samples. They are not part of any Azure service and are not covered by any SLA or other Azure-related agreements. They are provided as-is with no warranties express or implied. Microsoft takes no responsibility for the use of the scripts or the accuracy of this document. Familiarize yourself with the scripts before using them. + + + +## Related Links + + +This sample reuses code from [Azure SQL Connectivity Checker](https://github.com/Azure/SQL-Connectivity-Checker) diff --git a/samples/manage/get-tds-certificate/getTDSCertificate.ps1 b/samples/manage/get-tds-certificate/getTDSCertificate.ps1 new file mode 100644 index 00000000..ad79ee83 --- /dev/null +++ b/samples/manage/get-tds-certificate/getTDSCertificate.ps1 @@ -0,0 +1,644 @@ +$parameters = $args[0] + +$hostName = $parameters['hostName'] +$port = $parameters['port'] +$publicCertificateFile = $parameters['publicCertificateFile'] + +$Assem = @() + +$Source = @" + +using System; +using System.IO; + +namespace CL +{ + /// + /// Enum describing TDS Message Status + /// + public enum TDSMessageStatus : byte + { + /// + /// Normal TDS Message Status + /// + Normal, + + /// + /// TDS Message Terminator + /// The packet is the last packet in the whole request. + /// + EndOfMessage, + + /// + /// IgnoreEvent TDS Message Status + /// Ignore this event (0x01 MUST also be set). + /// + IgnoreEvent, + + /// + /// ResetConnection TDS Message Status + /// Reset this connection before processing event. + /// + ResetConnection = 0x08, + + /// + /// ResetConnectionSkipTran TDS Message Status + /// Reset the connection before processing event but do not modify the transaction + /// state (the state will remain the same before and after the reset). + /// + ResetConnectionSkipTran = 0x10 + } + + /// + /// Enum describing TDS Message Type + /// + public enum TDSMessageType : byte + { + /// + /// SQL Batch Message + /// + SQLBatch = 1, + + /// + /// TDS7 Pre Login Message + /// + PreTDS7Login, + + /// + /// RPC Message + /// + RPC, + + /// + /// Tabular Result Message + /// + TabularResult, + + /// + /// Attention Signal Message + /// + AttentionSignal = 6, + + /// + /// Bulk Load Data Message + /// + BulkLoadData, + + /// + /// Federated Authentication Token Message + /// + FedAuthToken, + + /// + /// Transaction Manager Request Message + /// + TransactionManagerRequest = 14, + + /// + /// TDS7 Login Message + /// + TDS7Login = 16, + + /// + /// SSPI Message + /// + SSPI, + + /// + /// PreLogin Message + /// + PreLogin + } + + /// + /// Utility class used for read and write operations on a stream containing data in big-endian byte order + /// + public static class BigEndianUtilities + { + /// + /// Used to write value to stream in big endian order. + /// + /// MemoryStream to to write the value to. + /// Value to write to MemoryStream. + public static void WriteUShort(MemoryStream stream, ushort value) + { + stream.WriteByte((byte)(value >> 8)); + stream.WriteByte((byte)value); + } + + /// + /// Used to write value to stream in big endian order. + /// + /// MemoryStream to to write the value to. + /// Value to write to MemoryStream. + public static void WriteUInt(MemoryStream stream, uint value) + { + stream.WriteByte((byte)(value >> 24)); + stream.WriteByte((byte)(value >> 16)); + stream.WriteByte((byte)(value >> 8)); + stream.WriteByte((byte)value); + } + + /// + /// Used to write value to stream in big endian order. + /// + /// MemoryStream to to write the value to. + /// Value to write to MemoryStream. + public static void WriteULong(MemoryStream stream, ulong value) + { + stream.WriteByte((byte)(value >> 56)); + stream.WriteByte((byte)(value >> 48)); + stream.WriteByte((byte)(value >> 40)); + stream.WriteByte((byte)(value >> 32)); + stream.WriteByte((byte)(value >> 24)); + stream.WriteByte((byte)(value >> 16)); + stream.WriteByte((byte)(value >> 8)); + stream.WriteByte((byte)value); + } + + /// + /// Used to write byte array to stream in big endian order. + /// + /// MemoryStream to to write the value to. + /// Array to write to MemoryStream. + public static void WriteByteArray(MemoryStream stream, byte[] array) + { + for (int i = array.Length - 1; i >= 0; i--) + { + stream.WriteByte(array[i]); + } + } + + /// + /// Used to read a UShort value from stream in big endian order. + /// + /// MemoryStream from which to read the value. + /// UShort value read from the stream. + public static ushort ReadUShort(MemoryStream stream) + { + ushort result = 0; + for (int i = 0; i < 2; i++) + { + result <<= 8; + result |= Convert.ToByte(stream.ReadByte()); + } + + return result; + } + + /// + /// Used to read a UInt value from stream in big endian order. + /// + /// MemoryStream from which to read the value. + /// UInt value read from the stream. + public static uint ReadUInt(MemoryStream stream) + { + uint result = 0; + for (int i = 0; i < 4; i++) + { + result <<= 8; + result |= Convert.ToByte(stream.ReadByte()); + } + + return result; + } + + /// + /// Used to read a ULong value from stream in big endian order. + /// + /// MemoryStream from which to read the value. + /// ULong value read from the stream. + public static ulong ReadULong(MemoryStream stream) + { + ulong result = 0; + for (int i = 0; i < 8; i++) + { + result <<= 8; + result |= Convert.ToByte(stream.ReadByte()); + } + + return result; + } + + /// + /// Used to read a byte array from stream in big endian order. + /// + /// MemoryStream from which to read the array. + /// Length of the array to read. + /// Byte Array read from the stream. + public static byte[] ReadByteArray(MemoryStream stream, uint length) + { + byte[] result = new byte[length]; + for (int i = 1; i <= length; i++) + { + result[length - i] = Convert.ToByte(stream.ReadByte()); + } + + return result; + } + } + + /// + /// Class describing TDS Packet Header + /// + public class TDSPacketHeader + { + /// + /// Initializes a new instance of the class. + /// + public TDSPacketHeader() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// TDS Message Type + /// TDS Message Status + /// SPID number + /// Packet number + /// Window number + public TDSPacketHeader(TDSMessageType type, TDSMessageStatus status, ushort spid = 0x0000, byte packet = 0x00, byte window = 0x00) + { + this.Type = type; + this.Status = status; + this.SPID = spid; + this.Packet = packet; + this.Window = window; + } + + /// + /// Gets or sets TDS Message Type. + /// + public TDSMessageType Type { get; set; } + + /// + /// Gets or sets TDS Message Status. + /// + public TDSMessageStatus Status { get; set; } + + /// + /// Gets or sets TDS Message Length. + /// + public ushort Length { get; set; } + + /// + /// Gets or sets SPID. + /// + public ushort SPID { get; set; } + + /// + /// Gets or sets Packet Number. + /// + public byte Packet { get; set; } + + /// + /// Gets or sets Window Number. + /// + public byte Window { get; set; } + + /// + /// Gets converted (to int) Packet Length. + /// + public int ConvertedPacketLength + { + get + { + return Convert.ToInt32(this.Length); + } + } + + /// + /// Used to pack IPackageable to a stream. + /// + /// MemoryStream in which IPackageable is packet into. + public void Pack(MemoryStream stream) + { + stream.WriteByte((byte)this.Type); + stream.WriteByte((byte)this.Status); + BigEndianUtilities.WriteUShort(stream, this.Length); + BigEndianUtilities.WriteUShort(stream, this.SPID); + stream.WriteByte(this.Packet); + stream.WriteByte(this.Window); + } + + /// + /// Used to unpack IPackageable from a stream. + /// + /// MemoryStream from which to unpack IPackageable. + /// Returns true if successful. + public bool Unpack(MemoryStream stream) + { + this.Type = (TDSMessageType)stream.ReadByte(); + this.Status = (TDSMessageStatus)stream.ReadByte(); + this.Length = BigEndianUtilities.ReadUShort(stream); + this.SPID = BigEndianUtilities.ReadUShort(stream); + this.Packet = Convert.ToByte(stream.ReadByte()); + this.Window = Convert.ToByte(stream.ReadByte()); + + return true; + } + } + + /// + /// Stream used to pass TDS messages. + /// + public class TDSStream : Stream + { + /// + /// TDS Packet Size used for communication + /// + private readonly int negotiatedPacketSize; + + /// + /// Current Inbound TDS Packet Header + /// + private TDSPacketHeader currentInboundTDSHeader; + + /// + /// Current position within the Inbound TDS Packet + /// + private int currentInboundPacketPosition; + + /// + /// Current Outbound TDS Packet Header + /// + private TDSPacketHeader currentOutboundTDSHeader; + + /// + /// TDS Connection Timeout + /// + private TimeSpan timeout; + + /// + /// Initializes a new instance of the class. + /// + /// Inner stream used for communication + /// Communication failure timeout + /// Packet size + public TDSStream(Stream innerStream, TimeSpan timeout, int negotiatedPacketSize) + { + this.InnerStream = innerStream; + this.timeout = timeout; + this.negotiatedPacketSize = negotiatedPacketSize; + } + + /// + /// Gets or sets the Inner Stream. + /// + public Stream InnerStream { get; set; } + + /// + /// Gets a value indicating whether inbound message is terminated. + /// + public bool InboundMessageTerminated + { + get + { + return this.currentInboundTDSHeader == null; + } + } + + /// + /// Gets or sets the current outbound message type. + /// + public TDSMessageType CurrentOutboundMessageType { get; set; } + + /// + /// Gets or sets CanTimeout Flag. + /// + public override bool CanTimeout { get { return true; } } + + /// + /// Gets or sets CanRead Flag. + /// + public override bool CanRead { get { return this.InnerStream.CanRead; } } + + /// + /// Gets or sets CanSeek Flag. + /// + public override bool CanSeek { get { return this.InnerStream.CanSeek; } } + + /// + /// Gets or sets CanWrite Flag. + /// + public override bool CanWrite { get { return this.InnerStream.CanWrite; } } + + /// + /// Gets or sets Stream Length. + /// + public override long Length { get { return this.InnerStream.Length; } } + + /// + /// Gets or sets Stream Position. + /// + public override long Position + { + get + { + return this.InnerStream.Position; + } + set + { + this.InnerStream.Position = value; + } + } + + /// + /// Flushes stream output. + /// + public override void Flush() + { + this.InnerStream.Flush(); + } + + /// + /// Reads from stream. + /// + /// Buffer used to store read data. + /// Offset within buffer. + /// Number of bytes to read. + /// Returns number of successfully read bytes. + public override int Read(byte[] buffer, int offset, int count) + { + var startTime = DateTime.Now; + var bytesReadTotal = 0; + + while (bytesReadTotal < count && DateTime.Now - this.timeout < startTime) + { + if (this.currentInboundTDSHeader == null || this.currentInboundPacketPosition >= this.currentInboundTDSHeader.ConvertedPacketLength) + { + byte[] headerBuffer = new byte[8]; + int curPos = 0; + do + { + curPos += this.InnerStream.Read(headerBuffer, curPos, 8 - curPos); + + if (curPos == 0) + { + throw new Exception("Failure to read from network stream."); + } + } + while (curPos < 8 && DateTime.Now - this.timeout < startTime); + + if (DateTime.Now - this.timeout >= startTime) + { + throw new TimeoutException("Reading from network stream timed out."); + } + + this.currentInboundTDSHeader = new TDSPacketHeader(); + this.currentInboundTDSHeader.Unpack(new MemoryStream(headerBuffer)); + this.currentInboundPacketPosition = 8; + } + + var bytesToReadFromCurrentPacket = Math.Min(count - bytesReadTotal, this.currentInboundTDSHeader.ConvertedPacketLength - this.currentInboundPacketPosition); + + do + { + var bytesRead = this.InnerStream.Read(buffer, offset + bytesReadTotal, bytesToReadFromCurrentPacket); + + if (bytesRead == 0) + { + throw new Exception("Failure to read from network stream."); + } + + bytesToReadFromCurrentPacket -= bytesRead; + this.currentInboundPacketPosition += bytesRead; + bytesReadTotal += bytesRead; + } + while (bytesToReadFromCurrentPacket > 0 && DateTime.Now - this.timeout < startTime); + + if (this.currentInboundTDSHeader != null && this.currentInboundPacketPosition >= this.currentInboundTDSHeader.ConvertedPacketLength && (this.currentInboundTDSHeader.Status & TDSMessageStatus.EndOfMessage) == TDSMessageStatus.EndOfMessage) + { + this.currentInboundTDSHeader = null; + return bytesReadTotal; + } + } + + if (DateTime.Now - this.timeout >= startTime) + { + throw new TimeoutException("Reading from network stream timed out."); + } + + return bytesReadTotal; + } + + /// + /// Write to stream. + /// + /// Buffer containing data that's being written. + /// Offset within buffer. + /// Number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + this.currentOutboundTDSHeader = new TDSPacketHeader(this.CurrentOutboundMessageType, TDSMessageStatus.Normal, 0, 1); + + var bytesSent = 0; + + while (bytesSent < count) + { + if (count - bytesSent + 8 < this.negotiatedPacketSize) + { + this.currentOutboundTDSHeader.Status = TDSMessageStatus.EndOfMessage; + } + + var bufferSize = Math.Min(count - bytesSent + 8, this.negotiatedPacketSize); + byte[] packetBuffer = new byte[bufferSize]; + + this.currentOutboundTDSHeader.Length = Convert.ToUInt16(bufferSize); + this.currentOutboundTDSHeader.Pack(new MemoryStream(packetBuffer)); + Array.Copy(buffer, offset + bytesSent, packetBuffer, 8, bufferSize - 8); + + this.InnerStream.Write(packetBuffer, 0, bufferSize); + bytesSent += bufferSize - 8; + + this.currentOutboundTDSHeader.Packet = (byte)((this.currentOutboundTDSHeader.Packet + 1) % 256); + } + } + + /// + /// Seek within stream. + /// + /// Offset from origin. + /// Origin to seek from. + /// The new position within current stream. + public override long Seek(long offset, SeekOrigin origin) + { + return this.InnerStream.Seek(offset, origin); + } + + /// + /// Set stream length. + /// + /// New length. + public override void SetLength(long value) + { + this.InnerStream.SetLength(value); + } + + /// + /// Close this stream. + /// + public override void Close() + { + this.InnerStream.Close(); + base.Close(); + } + } + +} + +"@ + +function Using-Object +{ + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [AllowEmptyString()] + [AllowEmptyCollection()] + [AllowNull()] + [Object] + $InputObject, + + [Parameter(Mandatory = $true)] + [scriptblock] + $ScriptBlock + ) + + try + { + . $ScriptBlock + } + finally + { + if ($null -ne $InputObject -and $InputObject -is [System.IDisposable]) + { + $InputObject.Dispose() + } + } +} + +Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp -ErrorAction SilentlyContinue + +$preLoginMessage = "AAAQAAYBABYAAQUAFwAk/wAAAAEAAACZ2dq6TF91Tqd3QhqNHRpv9/WwhLbfCUO6JBKeUqXXDAAAAAA=" +$tcpClient = New-Object System.Net.Sockets.TcpClient($hostName, $port) + + +Using-Object($stream = New-Object CL.TDSStream($tcpClient.GetStream(), [TimeSpan]::FromSeconds(30), 4096)) { + $stream.CurrentOutboundMessageType = [CL.TDSMessageType]::PreLogin + $writeBuffer = [Convert]::FromBase64String($preLoginMessage) + $stream.Write($writeBuffer, 0, $writeBuffer.Length) + $readBuffer = New-Object System.Byte[] 4096 + $bytesRead = $stream.Read($readBuffer, 0, $readBuffer.Length) + $sslStream = New-Object System.Net.Security.SslStream($stream, $true) + $sslStream.AuthenticateAsClient($hostName) + $certificate = $sslStream.RemoteCertificate + [System.IO.File]::WriteAllBytes($publicCertificateFile,$certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)) +} + + + + + +