From 3ac7fb5ec84aa276248bcc56bd5c42429db39a26 Mon Sep 17 00:00:00 2001 From: antiduh Date: Mon, 23 Jun 2014 21:50:12 +0000 Subject: [PATCH] Initial implementation of EncryptMessage and DecryptMessage. --- ByteWriter.cs | 52 ++++++++++ Contexts/Context.cs | 162 ++++++++++++++++++++++++++++++- Contexts/ContextNativeMethods.cs | 15 +++ NSspi.csproj | 9 +- NSspi.sln | 3 + Program.cs | 33 +++++++ 6 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 ByteWriter.cs diff --git a/ByteWriter.cs b/ByteWriter.cs new file mode 100644 index 0000000..088e591 --- /dev/null +++ b/ByteWriter.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace NSspi +{ + public static class ByteWriter + { + // Big endian: Most significant byte at lowest address in memory. + + public static void WriteInt16_BE( Int16 value, byte[] buffer, int position ) + { + buffer[position + 0] = (byte)( value >> 8 ); + buffer[position + 1] = (byte)( value ); + } + + public static void WriteInt32_BE( Int32 value, byte[] buffer, int position ) + { + buffer[position + 0] = (byte)( value >> 24 ); + buffer[position + 1] = (byte)( value >> 16 ); + buffer[position + 2] = (byte)( value >> 8 ); + buffer[position + 3] = (byte)( value); + + } + + public static Int16 ReadInt16_BE( byte[] buffer, int position ) + { + Int16 value; + + value = (Int16)( buffer[position + 0] << 8 ); + value += (Int16)( buffer[position + 1] ); + + return value; + } + + public static Int32 ReadInt32_BE( byte[] buffer, int position ) + { + Int32 value; + + value = (Int32)( buffer[position + 0] << 24 ); + value |= (Int32)( buffer[position + 1] << 16 ); + value |= (Int32)( buffer[position + 2] << 8 ); + value |= (Int32)( buffer[position + 3] ); + + return value; + } + + } +} diff --git a/Contexts/Context.cs b/Contexts/Context.cs index cd9d922..58b9873 100644 --- a/Contexts/Context.cs +++ b/Contexts/Context.cs @@ -72,12 +72,172 @@ namespace NSspi /// byte array, which is formatted such that the first four bytes are the original message length /// as an unsigned integer and the remaining bytes are the encrypted bytes of the original message. /// + /// + /// The resulting byte array stores the SSPI buffer data in the following buffer format: + /// - Token + /// - Data + /// - Padding + /// /// /// public byte[] Encrypt( byte[] input ) { // The message is encrypted in place in the buffer we provide to Win32 EncryptMessage - return null; + SecPkgContext_Sizes sizes = QueryBufferSizes(); + + SecureBuffer trailerBuffer; + SecureBuffer dataBuffer; + SecureBuffer paddingBuffer; + SecureBufferAdapter adapter; + + long contextHandle = this.ContextHandle; + + SecurityStatus status; + byte[] result; + + trailerBuffer = new SecureBuffer( new byte[sizes.SecurityTrailer], BufferType.Token ); + dataBuffer = new SecureBuffer( new byte[input.Length], BufferType.Data ); + paddingBuffer = new SecureBuffer( new byte[sizes.BlockSize], BufferType.Padding ); + + Array.Copy( input, dataBuffer.Buffer, input.Length ); + + using( adapter = new SecureBufferAdapter( new[] { trailerBuffer, dataBuffer, paddingBuffer } ) ) + { + status = ContextNativeMethods.EncryptMessage( + ref contextHandle, + 0, + adapter.Handle, + 0 + ); + } + + if( status != SecurityStatus.OK ) + { + throw new SSPIException( "Failed to encrypt message", status ); + } + + int position = 0; + + // Enough room to fit: + // -- 2 bytes for the trailer buffer size + // -- 4 bytes for the message size + // -- 2 bytes for the padding size. + // -- The encrypted message + result = new byte[2 + 4 + 2 + trailerBuffer.Length + dataBuffer.Length + paddingBuffer.Length]; + + ByteWriter.WriteInt16_BE( (short)trailerBuffer.Length, result, position ); + position += 2; + + ByteWriter.WriteInt32_BE( dataBuffer.Length, result, position ); + position += 4; + + ByteWriter.WriteInt16_BE( (short)paddingBuffer.Length, result, position ); + position += 2; + + Array.Copy( trailerBuffer.Buffer, 0, result, position, trailerBuffer.Length ); + position += trailerBuffer.Length; + + Array.Copy( dataBuffer.Buffer, 0, result, position, dataBuffer.Length ); + position += dataBuffer.Length; + + Array.Copy( paddingBuffer.Buffer, 0, result, position, paddingBuffer.Length ); + position += paddingBuffer.Length; + + return result; + } + + public byte[] Decrypt( byte[] input ) + { + SecPkgContext_Sizes sizes = QueryBufferSizes(); + + SecureBuffer trailerBuffer; + SecureBuffer dataBuffer; + SecureBuffer paddingBuffer; + SecureBufferAdapter adapter; + + long contextHandle = this.ContextHandle; + + SecurityStatus status; + byte[] result = null; + int remaining; + int position; + + int trailerLength; + int dataLength; + int paddingLength; + + // This check is required, but not sufficient. We could be stricter. + if( input.Length < 2 + 4 + 2 + sizes.SecurityTrailer ) + { + throw new ArgumentException( "Buffer is too small to possibly contain an encrypted message" ); + } + + position = 0; + + trailerLength = ByteWriter.ReadInt16_BE( input, position ); + position += 2; + + dataLength = ByteWriter.ReadInt32_BE( input, position ); + position += 4; + + paddingLength = ByteWriter.ReadInt16_BE( input, position ); + position += 2; + + + trailerBuffer = new SecureBuffer( new byte[trailerLength], BufferType.Token ); + dataBuffer = new SecureBuffer( new byte[dataLength], BufferType.Data ); + paddingBuffer = new SecureBuffer( new byte[paddingLength], BufferType.Padding ); + + remaining = input.Length - position; + + if( trailerBuffer.Length <= remaining ) + { + Array.Copy( input, position, trailerBuffer.Buffer, 0, trailerBuffer.Length ); + position += trailerBuffer.Length; + remaining -= trailerBuffer.Length; + } + else + { + throw new ArgumentException( "Input is missing data - it is not long enough to contain a fully encrypted message" ); + } + + if( dataBuffer.Length <= remaining ) + { + Array.Copy( input, position, dataBuffer.Buffer, 0, dataBuffer.Length ); + position += dataBuffer.Length; + remaining -= dataBuffer.Length; + } + else + { + throw new ArgumentException( "Input is missing data - it is not long enough to contain a fully encrypted message" ); + } + + if( paddingBuffer.Length <= remaining ) + { + Array.Copy( input, position, paddingBuffer.Buffer, 0, paddingBuffer.Length ); + } + // else there was no padding. + + + using( adapter = new SecureBufferAdapter( new [] { trailerBuffer, dataBuffer, paddingBuffer } ) ) + { + status = ContextNativeMethods.DecryptMessage( + ref contextHandle, + adapter.Handle, + 0, + 0 + ); + } + + if( status != SecurityStatus.OK ) + { + throw new SSPIException( "Failed to encrypt message", status ); + } + + result = new byte[dataBuffer.Length]; + Array.Copy( dataBuffer.Buffer, 0, result, 0, dataBuffer.Length ); + + return result; } internal SecPkgContext_Sizes QueryBufferSizes() diff --git a/Contexts/ContextNativeMethods.cs b/Contexts/ContextNativeMethods.cs index 7e1893d..bf9bb93 100644 --- a/Contexts/ContextNativeMethods.cs +++ b/Contexts/ContextNativeMethods.cs @@ -163,6 +163,21 @@ namespace NSspi.Contexts int sequenceNumber ); + + [DllImport( + "Secur32.dll", + EntryPoint = "DecryptMessage", + CallingConvention = CallingConvention.Winapi, + CharSet = CharSet.Unicode, + SetLastError = true + )] + public static extern SecurityStatus DecryptMessage( + ref long contextHandle, + IntPtr bufferDescriptor, + int sequenceNumber, + int qualityOfProtection + ); + [DllImport( "Secur32.dll", EntryPoint = "QueryContextAttributes", diff --git a/NSspi.csproj b/NSspi.csproj index ddd141a..034826b 100644 --- a/NSspi.csproj +++ b/NSspi.csproj @@ -15,12 +15,12 @@ true full - false + true bin\Debug\ DEBUG;TRACE prompt 4 - true + false pdbonly @@ -45,6 +45,7 @@ + @@ -71,9 +72,7 @@ - - - +