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

try to decode ESP frames #155

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
32 changes: 32 additions & 0 deletions PacketDotNet/EspFields.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
This file is part of PacketDotNet.

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2009 Chris Morgan <[email protected]>
*/

namespace PacketDotNet
{
public struct EspFields
{
/// <summary>Length of the Base Header in bytes.</summary>
public static readonly int HeaderLength = 8;

/// <summary>Length of the Security Parameters Index (SPI) in bytes.</summary>
public static readonly int SecurityParametersIndexLength = 4;

/// <summary>Length of the Sequence Number in bytes.</summary>
public static readonly int SequenceNumberLength = 4;

/// <summary>Length of the Pad Length in bytes.</summary>
public static readonly int PadLengthLength = 1;

/// <summary>Length of the Next Header in bytes.</summary>
public static readonly int NextHeaderLength = 1;

}
}
156 changes: 156 additions & 0 deletions PacketDotNet/EspPacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
This file is part of PacketDotNet.

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/*
* Copyright 2010 Chris Morgan <[email protected]>
*/


using System;
using PacketDotNet.Utils;
using PacketDotNet.Utils.Converters;
#if DEBUG
using log4net;
using System.Reflection;
#endif

namespace PacketDotNet
{
/// <summary>
/// EspPacket
/// See: https://en.wikipedia.org/wiki/IPsec#Encapsulating_Security_Payload
/// </summary>
public sealed class EspPacket : Packet
{
#if DEBUG
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
#else
// NOTE: No need to warn about lack of use, the compiler won't
// put any calls to 'log' here but we need 'log' to exist to compile
#pragma warning disable 0169, 0649
private static readonly ILogInactive Log;
#pragma warning restore 0169, 0649
#endif

/// <summary>
/// Create from values
/// </summary>
public EspPacket()
{
Log.Debug("");

// allocate memory for this packet
var length = EspFields.HeaderLength;
var headerBytes = new byte[length];
Header = new ByteArraySegment(headerBytes, 0, length);
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="byteArraySegment">
/// A <see cref="ByteArraySegment" />
/// </param>
/// <param name="parentPacket">
/// A <see cref="Packet" />
/// </param>
public EspPacket(ByteArraySegment byteArraySegment, Packet parentPacket)
{
Log.Debug("");

// set the header field, header field values are retrieved from this byte array
Header = new ByteArraySegment(byteArraySegment) {Length = EspFields.HeaderLength};

ParentPacket = parentPacket;

var next = Header.NextSegment();

// try to decode, assuming a Null ciphering. Get first the last 96 bits (12 bytes) for the Authentication Data
// + 1 byte for the Next Header + 1 byte for the pad length
if (next.Length > 14)
{
AuthenticationData = new byte[12];

// copy the last 12 bytes
Array.Copy(next.Bytes, next.BytesLength - 12, AuthenticationData, 0, 12);
var nextHeader = next.Bytes[next.BytesLength - 13];
// Continue only if next header is Tcp or Udp
if (Enum.IsDefined(typeof(ProtocolType), nextHeader) && ((ProtocolType)nextHeader == ProtocolType.Tcp || (ProtocolType)nextHeader == ProtocolType.Udp))
jgaulon marked this conversation as resolved.
Show resolved Hide resolved
{
NextHeader = (ProtocolType) nextHeader;
PadLength = next.Bytes[next.BytesLength - 14];

if (next.Length > 14 + PadLength)
{
// so far ok, continue
Pad = new byte[PadLength];
Array.Copy(next.Bytes, next.BytesLength - 14 - PadLength, Pad, 0, PadLength);

var startingOffset = Header.Offset + Header.Length;
var segmentLength = Math.Max(0, next.Length - 14 - PadLength);
var payloadData = new byte[segmentLength];
Array.Copy(next.Bytes, startingOffset, payloadData, 0, segmentLength);
var payload = new ByteArraySegment(payloadData, 0, segmentLength, segmentLength);

if (NextHeader == ProtocolType.Tcp && segmentLength > 0)
{
PayloadPacketOrData = new LazySlim<PacketOrByteArraySegment>(() => new PacketOrByteArraySegment
{
Packet = new TcpPacket(payload, this)
});

return;
}
}
}
}

// store the payload bytes
PayloadPacketOrData = new LazySlim<PacketOrByteArraySegment>(() =>
{
var result = new PacketOrByteArraySegment {ByteArraySegment = Header.NextSegment()};
return result;
});
}

/// <summary>
/// Gets or sets the SecurityParametersIndex (SPI)
jgaulon marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public uint SecurityParametersIndex
{
get => EndianBitConverter.Big.ToUInt32(Header.Bytes, Header.Offset);
set => EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset);
}

/// <summary>
/// Gets or sets the SecurityParametersIndex (SPI)
/// </summary>
public uint SequenceNumber
{
get => EndianBitConverter.Big.ToUInt32(Header.Bytes, Header.Offset + EspFields.SecurityParametersIndexLength);
set => EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset + EspFields.SecurityParametersIndexLength);
}

/// <summary>
/// Identifies the next header field, which is the protocol of the encapsulated in packet unless there are extended headers.
/// </summary>
public ProtocolType NextHeader { get; set; }

/// <summary>Pad length</summary>
public int PadLength { get; private set; }

/// <summary>
/// Gets or sets the Authentication Data
/// </summary>
public byte[] AuthenticationData { get; set; }

/// <summary>
/// Gets or sets the Pad
/// </summary>
public byte[] Pad { get; private set; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get and sets should be implemented for all of these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, I don't actually really know how to do this. The packet type is "Encapsulated Security Payload" and as the name says it, this is encapsulated. So that the "NextHeader", "Pad" and its length and finally the integrity signature are placed after the encrypted payload (even in case of Null Ciphering case, so no encryption). The Packet and ByteArraySegment classes are designed that all fields are stored in the Header which is here not the case, as this is a Suffix.
For decoding, I managed to find a way and use the simple setter/getter only. For encoding, this can't be done without the payload and the corresponding integrity algorithm and its key.
Any suggestion?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the header briefly, I think a set would be possible after reading the length of payload/segment? The code right now is also a bit strange as the data would become invalid once you change the payload. For the set, it could alternatively be changed into a method to pass in the required details. DHCP packet is doing something similar with Options, https://github.com/chmorgan/packetnet/blob/master/PacketDotNet/DhcpV4Packet.cs#L299

}
}
5 changes: 5 additions & 0 deletions PacketDotNet/IPPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ protected static PacketOrByteArraySegment ParseNextSegment
payloadPacketOrData.Packet = new GrePacket(payload, parentPacket);
break;
}
case ProtocolType.IPSecEncapsulatingSecurityPayload:
{
payloadPacketOrData.Packet = new EspPacket(payload, parentPacket);
break;
}
// NOTE: new payload parsing entries go here
default:
{
Expand Down
Binary file added Test/CaptureFiles/ipv6_esp.pcap
Binary file not shown.
59 changes: 59 additions & 0 deletions Test/PacketType/EspPacketTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
This file is part of PacketDotNet.

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

using System;
using NUnit.Framework;
using PacketDotNet;
using SharpPcap;
using SharpPcap.LibPcap;

namespace Test.PacketType
{
[TestFixture]
public class EspPacketTest
{
[Test]
public void EspParsing()
{
var dev = new CaptureFileReaderDevice(NUnitSetupClass.CaptureDirectory + "ipv6_esp.pcap");
dev.Open();
PacketCapture c;
dev.GetNextPacket(out c);
var rawCapture = c.GetPacket();
dev.Close();

var p = Packet.ParsePacket(rawCapture.GetLinkLayers(), rawCapture.Data);

Assert.IsNotNull(p);
var esp = p.Extract<EspPacket>();
Assert.IsNotNull(esp);
Console.WriteLine(esp.GetType());
Assert.AreEqual(156633505, esp.SecurityParametersIndex);
Assert.AreEqual(1, esp.SequenceNumber);
Assert.AreEqual(ProtocolType.Tcp, esp.NextHeader);
Assert.AreEqual(2, esp.PadLength);
Assert.IsNotNull(esp.PayloadPacket);
var tcp = p.Extract<TcpPacket>();
Assert.IsNotNull(tcp);
Assert.AreEqual(0, tcp.UrgentPointer);
Assert.AreEqual(16000, tcp.WindowSize);
Assert.AreEqual(4, tcp.OptionsCollection.Count);
}

[Test]
public void ConstructEspPacketFromValues()
{
var esp = new EspPacket
{
SequenceNumber = 1,
SecurityParametersIndex = 156633505,
NextHeader = ProtocolType.Tcp,
};
}
}
}