Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added option to encrypt/decrypt data without embedding buffer lengths #27

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 82 additions & 12 deletions NSspi/Contexts/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,17 +221,18 @@ out token
/// Encrypts the byte array using the context's session key.
/// </summary>
/// <remarks>
/// The structure of the returned data is as follows:
/// - 2 bytes, an unsigned big-endian integer indicating the length of the trailer buffer size
/// - 4 bytes, an unsigned big-endian integer indicating the length of the message buffer size.
/// - 2 bytes, an unsigned big-endian integer indicating the length of the encryption padding buffer size.
/// If <paramref name="stream"/> is <c>false</c>, this is equivalent to calling <see cref="Encrypt(byte[])"/> and
/// the returned array contains encoded versions of the trailer, message and padding buffers.
///
/// If <paramref name="stream"/> is <c>true</c> the lengths will be ommitted and the returned array contains only:
/// - The trailer buffer
/// - The message buffer
/// - The padding buffer.
/// </remarks>
/// <param name="input">The raw message to encrypt.</param>
/// <param name="stream">Indicates if the returned data should contain only the encrypted data (<c>true</c>) or also an encoded version of the length of each buffer (<c>false</c>)</param>
/// <returns>The packed and encrypted message.</returns>
public byte[] Encrypt( byte[] input )
public byte[] Encrypt( byte[] input, bool stream )
{
// The message is encrypted in place in the buffer we provide to Win32 EncryptMessage
SecPkgContext_Sizes sizes;
Expand Down Expand Up @@ -276,16 +277,19 @@ public byte[] Encrypt( byte[] input )
// -- 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];
result = new byte[(stream ? 0 : (2 + 4 + 2)) + trailerBuffer.Length + dataBuffer.Length + paddingBuffer.Length];

ByteWriter.WriteInt16_BE( (short)trailerBuffer.Length, result, position );
position += 2;
if( !stream )
{
ByteWriter.WriteInt16_BE( (short)trailerBuffer.Length, result, position );
position += 2;

ByteWriter.WriteInt32_BE( dataBuffer.Length, result, position );
position += 4;
ByteWriter.WriteInt32_BE( dataBuffer.Length, result, position );
position += 4;

ByteWriter.WriteInt16_BE( (short)paddingBuffer.Length, result, position );
position += 2;
ByteWriter.WriteInt16_BE( (short)paddingBuffer.Length, result, position );
position += 2;
}

Array.Copy( trailerBuffer.Buffer, 0, result, position, trailerBuffer.Length );
position += trailerBuffer.Length;
Expand All @@ -299,6 +303,72 @@ public byte[] Encrypt( byte[] input )
return result;
}

/// <summary>
/// Encrypts the byte array using the context's session key.
/// </summary>
/// <remarks>
/// The structure of the returned data is as follows:
/// - 2 bytes, an unsigned big-endian integer indicating the length of the trailer buffer size
/// - 4 bytes, an unsigned big-endian integer indicating the length of the message buffer size.
/// - 2 bytes, an unsigned big-endian integer indicating the length of the encryption padding buffer size.
/// - The trailer buffer
/// - The message buffer
/// - The padding buffer.
/// </remarks>
/// <param name="input">The raw message to encrypt.</param>
/// <returns>The packed and encrypted message.</returns>
public byte[] Encrypt( byte[] input )
{
return Encrypt( input, false );
}

/// <summary>
/// Decrypts a previously encrypted message.
/// </summary>
/// <remarks>
/// If <paramref name="stream"/> is <c>false</c>, this is equivalent to calling <see cref="Decrypt(byte[])"/> and
/// the <paramref name="input"/> must contain encoded versions of the trailer, message and padding buffers.
///
/// If <paramref name="stream"/> is <c>true</c> the lengths should be ommitted and the <paramref name="input"/>
/// should contain only:
/// - The trailer buffer
/// - The message buffer
/// - The padding buffer.
/// </remarks>
/// <param name="input">The packed and encrypted data.</param>
/// <param name="stream">Indicates if the data contains only the encrypted data (<c>true</c>) or also contains an encoded version of the length of each buffer (<c>false</c>)</param>
/// <returns>The original plaintext message.</returns>
public byte[] Decrypt( byte[] input, bool stream )
{
if( !stream )
{
return Decrypt( input );
}

byte[] inputCopy = new byte[input.Length];
Array.Copy( input, 0, inputCopy, 0, input.Length );

SecureBuffer inputBuffer;
SecureBuffer outputBuffer;
SecureBufferAdapter adapter;
SecurityStatus status;

inputBuffer = new SecureBuffer( inputCopy, BufferType.Stream );
outputBuffer = new SecureBuffer( null, BufferType.Data );

using( adapter = new SecureBufferAdapter( new[] { inputBuffer, outputBuffer } ) )
{
status = ContextNativeMethods.SafeDecryptMessage(
this.ContextHandle,
0,
adapter,
0
);

return adapter.ExtractData(1);
}
}

/// <summary>
/// Decrypts a previously encrypted message.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion NSspi/SecureBuffer/SecureBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public SecureBuffer( byte[] buffer, BufferType type )
{
this.Buffer = buffer;
this.Type = type;
this.Length = this.Buffer.Length;
this.Length = this.Buffer?.Length ?? 0;
}

/// <summary>
Expand Down
19 changes: 18 additions & 1 deletion NSspi/SecureBuffer/SecureBufferAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public SecureBufferAdapter( IList<SecureBuffer> buffers ) : base()

this.bufferCarrier[i] = new SecureBufferInternal();
this.bufferCarrier[i].Type = this.buffers[i].Type;
this.bufferCarrier[i].Count = this.buffers[i].Buffer.Length;
this.bufferCarrier[i].Count = this.buffers[i].Buffer?.Length ?? 0;
this.bufferCarrier[i].Buffer = bufferHandles[i].AddrOfPinnedObject();
}

Expand All @@ -137,6 +137,23 @@ public SecureBufferAdapter( IList<SecureBuffer> buffers ) : base()
this.descriptorHandle = GCHandle.Alloc( descriptor, GCHandleType.Pinned );
}

/// <summary>
/// Extracts data from a buffer allocated by the platform
/// </summary>
/// <remarks>
/// If a buffer is allocated by the platform, the original .NET byte[] would be <c>null</c> and so can't
/// be read directly by the caller. This method extracts the data from the unmanaged buffer and copies it
/// to managed memory so it can be used easily.
/// </remarks>
/// <param name="index">The index of the buffer to extract the data from</param>
/// <returns>The data in the buffer</returns>
public byte[] ExtractData(int index)
{
byte[] buf = new byte[this.bufferCarrier[index].Count];
Marshal.Copy( this.bufferCarrier[index].Buffer, buf, 0, this.bufferCarrier[index].Count );
return buf;
}

[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )]
~SecureBufferAdapter()
{
Expand Down
24 changes: 24 additions & 0 deletions NsspiDemo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,30 @@ private static void CredTest( string packageName )
}
}

cipherText = client.Encrypt(plainText, true);

roundTripPlaintext = server.Decrypt(cipherText, true);

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();
}
finally
Expand Down