Skip to content

Commit

Permalink
Added LdapPacket.ParsePacketAsync
Browse files Browse the repository at this point in the history
  • Loading branch information
Gxost committed Jun 7, 2024
1 parent c0388b9 commit 19280e3
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 26 deletions.
19 changes: 16 additions & 3 deletions Flexinets.Ldap.Core.Tests/BerLengthTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using NUnit.Framework;
using System;
using System.Collections;
using System.IO;
using System.Threading.Tasks;
using NUnit.Framework;

namespace Flexinets.Ldap.Core.Tests
{
Expand Down Expand Up @@ -42,6 +42,19 @@ public void TestBerToInt(string input, int expected)
Assert.AreEqual(expected, intlength);
}

[TestCase("8400000159", 345)]
[TestCase("840000014f", 335)]
[TestCase("840000012b", 299)]
public async Task TestBerToIntAsync(string input, int expected)
{
var bytes = Utils.StringToByteArray(input);
var stream = new MemoryStream(bytes, 0, bytes.Length, false);

var result = await Utils.BerLengthToIntAsync(stream);

Assert.AreEqual(expected, result.Length);
}


[TestCase(1, "01")]
[TestCase(127, "7f")]
Expand Down
67 changes: 66 additions & 1 deletion Flexinets.Ldap.Core.Tests/LdapPacketTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace Flexinets.Ldap.Core.Tests
{
Expand Down Expand Up @@ -101,6 +102,18 @@ public void TestLdapAttributeParseFromStream4()
Assert.AreEqual(expected, Utils.ByteArrayToString(packet.GetBytes()));
}

[TestCase]
public async Task TestLdapAttributeParseFromStreamAsync4()
{
var bytes = "30620201026340041164633d636f6d70616e792c64633d636f6d0a01020a010302010202010b010100a31a040e73414d4163636f756e744e616d65040876666f7274656c693000a01b30190417322e31362e3834302e312e3131333733302e332e342e3200000000";
var expected = "30620201026340041164633d636f6d70616e792c64633d636f6d0a01020a010302010202010b010100a31a040e73414d4163636f756e744e616d65040876666f7274656c693000a01b30190417322e31362e3834302e312e3131333733302e332e342e32";
var packetBytes = Utils.StringToByteArray(bytes);
var stream = new MemoryStream(packetBytes);
var packet = await LdapPacket.ParsePacketAsync(stream);

Assert.NotNull(packet);
Assert.AreEqual(expected, Utils.ByteArrayToString(packet.GetBytes()));
}

[TestCase]
public void TestPacketMessageId()
Expand All @@ -122,6 +135,20 @@ public void TestPacketParsingBindRequest()
Assert.AreEqual(expected, Utils.ByteArrayToString(packet.GetBytes()));
}

[TestCase]
public async Task TestPacketParsingBindRequestAsync()
{
var bytes = "30490201016044020103042d636e3d62696e64557365722c636e3d55736572732c64633d6465762c64633d636f6d70616e792c64633d636f6d801062696e645573657250617373776f7264";
var expected = "30490201016044020103042d636e3d62696e64557365722c636e3d55736572732c64633d6465762c64633d636f6d70616e792c64633d636f6d801062696e645573657250617373776f7264";
var packetBytes = Utils.StringToByteArray(bytes);
var stream = new MemoryStream(packetBytes);
var packet = await LdapPacket.ParsePacketAsync(stream);
RecurseAttributes(packet);

Assert.NotNull(packet);
Assert.AreEqual(expected, Utils.ByteArrayToString(packet.GetBytes()));
}


[TestCase]
public void TestPacketStuff()
Expand Down Expand Up @@ -210,6 +237,26 @@ public void TestPacketParsingmorethan255()
Assert.AreEqual(expected, output);
}

/// <summary>
/// Example from https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=ldap-krb5-sign-seal-01.cap
/// Parse and rebuild a packet
/// </summary>
[TestCase]
public async Task TestPacketParsingmorethan255Async()
{
var bytes = "308400000159020200d563840000014f04000a01000a0100020100020178010100870b6f626a656374636c61737330840000012b0411737562736368656d61537562656e747279040d6473536572766963654e616d65040e6e616d696e67436f6e7465787473041464656661756c744e616d696e67436f6e746578740413736368656d614e616d696e67436f6e74657874041a636f6e66696775726174696f6e4e616d696e67436f6e746578740417726f6f74446f6d61696e4e616d696e67436f6e746578740410737570706f72746564436f6e74726f6c0414737570706f727465644c44415056657273696f6e0415737570706f727465644c444150506f6c69636965730417737570706f727465645341534c4d656368616e69736d73040b646e73486f73744e616d65040f6c646170536572766963654e616d65040a7365727665724e616d650415737570706f727465644361706162696c6974696573";
var expected = bytes;
var packetBytes = Utils.StringToByteArray(bytes);
var stream = new MemoryStream(packetBytes);
var packet = await LdapPacket.ParsePacketAsync(stream);
RecurseAttributes(packet);
Assert.NotNull(packet);
var output = Utils.ByteArrayToString(packet.GetBytes());
Console.WriteLine(bytes);
Console.WriteLine(output);
Assert.AreEqual(expected, output);
}


/// <summary>
/// Example from https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=ldap-krb5-sign-seal-01.cap
Expand All @@ -231,7 +278,25 @@ public void TestPacketThunderbirdSearch()
}



/// <summary>
/// Example from https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=ldap-krb5-sign-seal-01.cap
/// Parse and rebuild a packet
/// </summary>
[TestCase]
public async Task TestPacketThunderbirdSearchAsync()
{
var bytes = "30840000048e020102638400000485041164633d6578616d706c652c64633d636f6d0a01020a0100020164020100010100a08400000105a184000000ffa40d0402636e30078105686d6d6d6da4140409676976656e4e616d6530078105686d6d6d6da40d0402736e30078105686d6d6d6da41a040f6d6f7a696c6c614e69636b6e616d6530078105686d6d6d6da40f04046d61696c30078105686d6d6d6da41d04126d6f7a696c6c615365636f6e64456d61696c30078105686d6d6d6da018a416040b6465736372697074696f6e30078105686d6d6d6da40c04016f30078105686d6d6d6da40d04026f7530078105686d6d6d6da41004057469746c6530078105686d6d6d6da419040e6d6f7a696c6c61576f726b55726c30078105686d6d6d6da419040e6d6f7a696c6c61486f6d6555726c30078105686d6d6d6d30840000035204057469746c650402736e04077375726e616d6504176d6f7a696c6c61486f6d654c6f63616c6974794e616d650402636e040a636f6d6d6f6e6e616d650409676976656e4e616d6504106d6f7a696c6c61486f6d65537461746504046d61696c04016f0407636f6d70616e7904126d6f7a696c6c61486f6d6553747265657432040f6d6f7a696c6c614e69636b6e616d650410786d6f7a696c6c616e69636b6e616d6504066d6f62696c65040963656c6c70686f6e65040863617270686f6e65040f6d6f6469667974696d657374616d7004076e7341494d696404116e73637061696d73637265656e6e616d65040f74656c6570686f6e654e756d6265720409626972746879656172040163040b636f756e7472796e616d6504116d6f7a696c6c61486f6d65537472656574040a706f7374616c436f646504037a6970040e6d6f7a696c6c61437573746f6d310407637573746f6d3104166d6f7a696c6c61486f6d65436f756e7472794e616d65040273740406726567696f6e040e6d6f7a696c6c61437573746f6d320407637573746f6d32040e6d6f7a696c6c61486f6d6555726c0407686f6d6575726c04126d6f7a696c6c61576f726b5374726565743204126d6f7a696c6c615365636f6e64456d61696c0413786d6f7a696c6c617365636f6e64656d61696c041866616373696d696c6574656c6570686f6e656e756d6265720403666178040b6465736372697074696f6e04056e6f746573040e6d6f7a696c6c61437573746f6d330407637573746f6d330409686f6d6550686f6e6504126d6f7a696c6c6155736548746d6c4d61696c0413786d6f7a696c6c6175736568746d6c6d61696c040862697274686461790406737472656574040d73747265657461646472657373040d706f73744f6666696365426f78040e6d6f7a696c6c61437573746f6d340407637573746f6d3404016c04086c6f63616c69747904057061676572040a706167657270686f6e6504026f75040a6465706172746d656e7404106465706172746d656e746e756d62657204076f7267756e6974040a62697274686d6f6e7468040e6d6f7a696c6c61576f726b55726c0407776f726b75726c040a6c6162656c656455524904156d6f7a696c6c61486f6d65506f7374616c436f6465040b6f626a656374436c617373";
var expected = bytes;
var packetBytes = Utils.StringToByteArray(bytes);
var stream = new MemoryStream(packetBytes);
var packet = await LdapPacket.ParsePacketAsync(stream);
RecurseAttributes(packet);
Assert.NotNull(packet);
var output = Utils.ByteArrayToString(packet.GetBytes());
Console.WriteLine(bytes);
Console.WriteLine(output);
Assert.AreEqual(expected, output);
}



Expand Down
48 changes: 42 additions & 6 deletions Flexinets.Ldap.Core/LdapPacket.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using System;
using System.Diagnostics;
using System.IO;

using System.Threading;
using System.Threading.Tasks;

namespace Flexinets.Ldap.Core
{
Expand Down Expand Up @@ -45,11 +46,11 @@ public static LdapPacket ParsePacket(byte[] bytes)


/// <summary>
/// Try parsing an ldap packet from a stream
/// Try parsing an ldap packet from a stream
/// </summary>
/// <param name="stream"></param>
/// <param name="packet"></param>
/// <returns>True if succesful. False if parsing fails or stream is empty</returns>
/// <returns>True if successful. False if parsing fails or stream is empty</returns>
public static bool TryParsePacket(Stream stream, out LdapPacket? packet)
{
try
Expand All @@ -60,10 +61,9 @@ public static bool TryParsePacket(Stream stream, out LdapPacket? packet)
{
var contentLength = Utils.BerLengthToInt(stream, out int n);
var contentBytes = new byte[contentLength];
stream.Read(contentBytes, 0, contentLength);
_ = stream.Read(contentBytes, 0, contentLength);

packet = new LdapPacket(Tag.Parse(tagByte[0]));
packet.ChildAttributes.AddRange(ParseAttributes(contentBytes, 0, contentLength));
packet = ParsePacket(tagByte[0], contentBytes);
return true;
}
}
Expand All @@ -75,5 +75,41 @@ public static bool TryParsePacket(Stream stream, out LdapPacket? packet)
packet = null;
return false;
}

/// <summary>
/// Try parsing an ldap packet from a stream asynchronously
/// </summary>
/// <param name="stream">Stream</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Task returning packet if successful</returns>
public static async Task<LdapPacket?> ParsePacketAsync(Stream stream, CancellationToken cancellationToken = default)
{
try
{
var tagByte = new byte[1];
var i = await stream.ReadAsync(tagByte, 0, 1, cancellationToken).ConfigureAwait(false);
if (i != 0)
{
(var contentLength, _) = await Utils.BerLengthToIntAsync(stream, cancellationToken).ConfigureAwait(false);
var contentBytes = new byte[contentLength];
_ = await stream.ReadAsync(contentBytes, 0, contentLength, cancellationToken).ConfigureAwait(false);

return ParsePacket(tagByte[0], contentBytes);
}
}
catch (Exception ex)
{
Trace.TraceError($"Could not parse packet from stream {ex.Message}");
}

return null;
}

private static LdapPacket ParsePacket(byte tagByte, byte[] contentBytes)
{
var packet = new LdapPacket(Tag.Parse(tagByte));
packet.ChildAttributes.AddRange(ParseAttributes(contentBytes, 0, contentBytes.Length));
return packet;
}
}
}
60 changes: 44 additions & 16 deletions Flexinets.Ldap.Core/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Text;
using System.Linq;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Flexinets.Ldap.Core
{
Expand Down Expand Up @@ -101,7 +103,6 @@ public static int BerLengthToInt(byte[] bytes, int offset, out int berByteCount)
return BerLengthToInt(stream, out berByteCount);
}


/// <summary>
/// Get a BER length from a stream
/// </summary>
Expand All @@ -110,32 +111,59 @@ public static int BerLengthToInt(byte[] bytes, int offset, out int berByteCount)
/// <returns></returns>
public static int BerLengthToInt(Stream stream, out int berByteCount)
{
berByteCount = 1; // The minimum length of a ber encoded length is 1 byte
int attributeLength = 0;
var berByte = new byte[1];
stream.Read(berByte, 0, 1);
if (berByte[0] >> 7 == 1) // Long notation, first byte tells us how many bytes are used for the length
_ = stream.Read(berByte, 0, 1);
if (!IsLongNotation(berByte[0], out int lengthBytesLength))
{
var lengthoflengthbytes = berByte[0] & 127;
var lengthBytes = new byte[lengthoflengthbytes];
stream.Read(lengthBytes, 0, lengthoflengthbytes);
Array.Reverse(lengthBytes);
Array.Resize(ref lengthBytes, 4); // this will of course explode if length is larger than a 32 bit integer
attributeLength = BitConverter.ToInt32(lengthBytes, 0);
berByteCount += lengthoflengthbytes;
berByteCount = 1;
return berByte[0];
}
else // Short notation, length contained in the first byte

var lengthBytes = new byte[lengthBytesLength];
_ = stream.Read(lengthBytes, 0, lengthBytesLength);

berByteCount = lengthBytesLength + 1;
return GetLongLength(lengthBytes);
}

/// <summary>
/// Get a BER length from a stream asynchronously
/// </summary>
/// <param name="stream">Stream at position where BER length should be found</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Task returning tuple containing length and byte count</returns>
public static async Task<(int Length, int ByteCount)> BerLengthToIntAsync(Stream stream, CancellationToken cancellationToken = default)
{
var berByte = new byte[1];
_ = await stream.ReadAsync(berByte, 0, 1, cancellationToken).ConfigureAwait(false);
if (!IsLongNotation(berByte[0], out int lengthBytesLength))
{
attributeLength = berByte[0] & 127;
return (berByte[0], 1);
}

return attributeLength;
}
var lengthBytes = new byte[lengthBytesLength];
_ = await stream.ReadAsync(lengthBytes, 0, lengthBytesLength, cancellationToken).ConfigureAwait(false);

return (GetLongLength(lengthBytes), lengthBytesLength + 1);
}

public static string Repeat(string stuff, int n)
{
return string.Concat(Enumerable.Repeat(stuff, n));
}

private static bool IsLongNotation(byte berByte, out int lengthBytesLength)
{
bool isLongNotation = berByte >> 7 == 1;
lengthBytesLength = isLongNotation? berByte & 127 : 0;

return isLongNotation;
}

private static int GetLongLength(byte[] bytes)
{
Array.Reverse(bytes);
return BitConverter.ToInt32(bytes.AsSpan(0 ,4)); // this will of course explode if length is larger than a 32 bit integer
}
}
}

0 comments on commit 19280e3

Please sign in to comment.