Initial implementation of EncryptMessage and DecryptMessage.

This commit is contained in:
antiduh
2014-06-23 21:50:12 +00:00
parent 9785183f31
commit 3ac7fb5ec8
6 changed files with 268 additions and 6 deletions

52
ByteWriter.cs Normal file
View File

@@ -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;
}
}
}

View File

@@ -72,12 +72,172 @@ namespace NSspi
/// byte array, which is formatted such that the first four bytes are the original message length /// 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. /// as an unsigned integer and the remaining bytes are the encrypted bytes of the original message.
/// </summary> /// </summary>
/// <remarks>
/// The resulting byte array stores the SSPI buffer data in the following buffer format:
/// - Token
/// - Data
/// - Padding
/// </remarks>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
public byte[] Encrypt( byte[] input ) public byte[] Encrypt( byte[] input )
{ {
// The message is encrypted in place in the buffer we provide to Win32 EncryptMessage // 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() internal SecPkgContext_Sizes QueryBufferSizes()

View File

@@ -163,6 +163,21 @@ namespace NSspi.Contexts
int sequenceNumber 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( [DllImport(
"Secur32.dll", "Secur32.dll",
EntryPoint = "QueryContextAttributes", EntryPoint = "QueryContextAttributes",

View File

@@ -15,12 +15,12 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@@ -45,6 +45,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ByteWriter.cs" />
<Compile Include="Contexts\ClientContext.cs" /> <Compile Include="Contexts\ClientContext.cs" />
<Compile Include="Contexts\Context.cs" /> <Compile Include="Contexts\Context.cs" />
<Compile Include="Contexts\ContextAttrib.cs" /> <Compile Include="Contexts\ContextAttrib.cs" />
@@ -71,9 +72,7 @@
<Compile Include="SecurityStatus.cs" /> <Compile Include="SecurityStatus.cs" />
<Compile Include="SSPIException.cs" /> <Compile Include="SSPIException.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup />
<Folder Include="SecureBuffers\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -17,4 +17,7 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal EndGlobal

View File

@@ -101,6 +101,39 @@ namespace NSspi
Console.Out.WriteLine( "Client authority: " + client.AuthorityName ); Console.Out.WriteLine( "Client authority: " + client.AuthorityName );
Console.Out.WriteLine( "Client context user: " + client.ContextUserName ); Console.Out.WriteLine( "Client context user: " + client.ContextUserName );
string message = "Hello, world. This is a long message that will be encrypted";
string rtMessage;
byte[] plainText = new byte[Encoding.UTF8.GetByteCount( message )];
byte[] cipherText;
byte[] roundTripPlaintext;
Encoding.UTF8.GetBytes( message, 0, message.Length, plainText, 0 );
cipherText = client.Encrypt( plainText );
roundTripPlaintext = server.Decrypt( cipherText );
if( roundTripPlaintext.Length != plainText.Length )
{
throw new Exception();
}
for( int i= 0; i < plainText.Length; i++ )
{
if( plainText[i] != roundTripPlaintext[i] )
{
throw new Exception();
}
}
rtMessage = Encoding.UTF8.GetString( roundTripPlaintext, 0, roundTripPlaintext.Length );
if( rtMessage.Equals( message ) == false )
{
throw new Exception();
}
Console.Out.Flush(); Console.Out.Flush();
} }
finally finally