From 1daf3a47970b4be566ce9d4f5b0a32d5e5cf40c7 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Tue, 8 Mar 2022 15:20:39 +0000 Subject: [PATCH] Added option to encrypt/decrypt data without embedding buffer lengths --- NSspi/Contexts/Context.cs | 94 ++++++++++++++++++++--- NSspi/SecureBuffer/SecureBuffer.cs | 2 +- NSspi/SecureBuffer/SecureBufferAdapter.cs | 19 ++++- NsspiDemo/Program.cs | 24 ++++++ 4 files changed, 125 insertions(+), 14 deletions(-) diff --git a/NSspi/Contexts/Context.cs b/NSspi/Contexts/Context.cs index 67525bd..92bc0a4 100644 --- a/NSspi/Contexts/Context.cs +++ b/NSspi/Contexts/Context.cs @@ -221,17 +221,18 @@ out token /// Encrypts the byte array using the context's session key. /// /// - /// 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 is false, this is equivalent to calling and + /// the returned array contains encoded versions of the trailer, message and padding buffers. + /// + /// If is true the lengths will be ommitted and the returned array contains only: /// - The trailer buffer /// - The message buffer /// - The padding buffer. /// /// The raw message to encrypt. + /// Indicates if the returned data should contain only the encrypted data (true) or also an encoded version of the length of each buffer (false) /// The packed and encrypted message. - 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; @@ -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; @@ -299,6 +303,72 @@ public byte[] Encrypt( byte[] input ) return result; } + /// + /// Encrypts the byte array using the context's session key. + /// + /// + /// 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. + /// + /// The raw message to encrypt. + /// The packed and encrypted message. + public byte[] Encrypt( byte[] input ) + { + return Encrypt( input, false ); + } + + /// + /// Decrypts a previously encrypted message. + /// + /// + /// If is false, this is equivalent to calling and + /// the must contain encoded versions of the trailer, message and padding buffers. + /// + /// If is true the lengths should be ommitted and the + /// should contain only: + /// - The trailer buffer + /// - The message buffer + /// - The padding buffer. + /// + /// The packed and encrypted data. + /// Indicates if the data contains only the encrypted data (true) or also contains an encoded version of the length of each buffer (false) + /// The original plaintext message. + 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); + } + } + /// /// Decrypts a previously encrypted message. /// diff --git a/NSspi/SecureBuffer/SecureBuffer.cs b/NSspi/SecureBuffer/SecureBuffer.cs index 8ce6d1b..d06b034 100644 --- a/NSspi/SecureBuffer/SecureBuffer.cs +++ b/NSspi/SecureBuffer/SecureBuffer.cs @@ -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; } /// diff --git a/NSspi/SecureBuffer/SecureBufferAdapter.cs b/NSspi/SecureBuffer/SecureBufferAdapter.cs index 7da36a2..61d2a53 100644 --- a/NSspi/SecureBuffer/SecureBufferAdapter.cs +++ b/NSspi/SecureBuffer/SecureBufferAdapter.cs @@ -123,7 +123,7 @@ public SecureBufferAdapter( IList 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(); } @@ -137,6 +137,23 @@ public SecureBufferAdapter( IList buffers ) : base() this.descriptorHandle = GCHandle.Alloc( descriptor, GCHandleType.Pinned ); } + /// + /// Extracts data from a buffer allocated by the platform + /// + /// + /// If a buffer is allocated by the platform, the original .NET byte[] would be null 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. + /// + /// The index of the buffer to extract the data from + /// The data in the buffer + 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() { diff --git a/NsspiDemo/Program.cs b/NsspiDemo/Program.cs index 0d40d68..d0767f2 100644 --- a/NsspiDemo/Program.cs +++ b/NsspiDemo/Program.cs @@ -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