From 805d75ca1f33ff33951d5082e3d3535c7a090808 Mon Sep 17 00:00:00 2001 From: seladb Date: Tue, 23 Apr 2024 00:37:51 -0700 Subject: [PATCH] Introduce ASN.1 BER decoder and encoder (#1353) --- Common++/header/Logger.h | 2 +- Packet++/CMakeLists.txt | 2 + Packet++/header/Asn1Codec.h | 466 ++++++++++++++++++++ Packet++/src/Asn1Codec.cpp | 574 +++++++++++++++++++++++++ README.md | 29 +- Tests/Packet++Test/CMakeLists.txt | 1 + Tests/Packet++Test/TestDefinition.h | 4 + Tests/Packet++Test/Tests/Asn1Tests.cpp | 524 ++++++++++++++++++++++ Tests/Packet++Test/main.cpp | 3 + 9 files changed, 1590 insertions(+), 15 deletions(-) create mode 100644 Packet++/header/Asn1Codec.h create mode 100644 Packet++/src/Asn1Codec.cpp create mode 100644 Tests/Packet++Test/Tests/Asn1Tests.cpp diff --git a/Common++/header/Logger.h b/Common++/header/Logger.h index a32c344205..ad73716274 100644 --- a/Common++/header/Logger.h +++ b/Common++/header/Logger.h @@ -58,6 +58,7 @@ namespace pcpp PacketLogModuleRawPacket, ///< RawPacket module (Packet++) PacketLogModulePacket, ///< Packet module (Packet++) PacketLogModuleLayer, ///< Layer module (Packet++) + PacketLogModuleAsn1Codec, ///< Asn1Codec module (Packet++) PacketLogModuleArpLayer, ///< ArpLayer module (Packet++) PacketLogModuleEthLayer, ///< EthLayer module (Packet++) PacketLogModuleIPv4Layer, ///< IPv4Layer module (Packet++) @@ -93,7 +94,6 @@ namespace pcpp PacketLogModuleTelnetLayer, ///< TelnetLayer module (Packet++) PacketLogModuleStpLayer, ///< StpLayer module (Packet++) PacketLogModuleLLCLayer, ///< LLCLayer module (Packet++) - PacketLogModuleSingleCommandTextProtocolLayer, ///< SingleCommandTextProtocol module (Packet++) PacketLogModuleNdpLayer, ///< NdpLayer module (Packet++) PacketLogModuleFtpLayer, ///< FtpLayer module (Packet++) PacketLogModuleSomeIpLayer, ///< SomeIpLayer module (Packet++) diff --git a/Packet++/CMakeLists.txt b/Packet++/CMakeLists.txt index d0a05c4046..a0c4143929 100644 --- a/Packet++/CMakeLists.txt +++ b/Packet++/CMakeLists.txt @@ -2,6 +2,7 @@ add_library( Packet++ $ src/ArpLayer.cpp + src/Asn1Codec.cpp src/BgpLayer.cpp src/CotpLayer.cpp src/DhcpLayer.cpp @@ -67,6 +68,7 @@ add_library( set(public_headers header/ArpLayer.h + header/Asn1Codec.h header/BgpLayer.h header/CotpLayer.h header/DhcpLayer.h diff --git a/Packet++/header/Asn1Codec.h b/Packet++/header/Asn1Codec.h new file mode 100644 index 0000000000..719a75ffa1 --- /dev/null +++ b/Packet++/header/Asn1Codec.h @@ -0,0 +1,466 @@ +#pragma once + +#include +#include +#include +#include +#include "PointerVector.h" + +/// @file + +/** + * \namespace pcpp + * \brief The main namespace for the PcapPlusPlus lib + */ +namespace pcpp +{ + /** + * An enum for representing ASN.1 tag class + */ + enum class Asn1TagClass : uint8_t + { + /** The Universal tag class */ + Universal, + /** The Application tag class */ + Application, + /** The Context-Specific tag class */ + ContextSpecific, + /** The Private tag class */ + Private + }; + + /** + * An enum for representing ASN.1 Universal tag types + */ + enum class Asn1UniversalTagType : uint8_t + { + /** The reserved identifier for the End-of-Contents marker in an indefinite length encoding */ + EndOfContent = 0, + /** The universal tag type for Boolean */ + Boolean = 1, + /** The universal tag type for Integer */ + Integer = 2, + /** The universal tag type for Bit String */ + BitString = 3, + /** The universal tag type for Octet String */ + OctetString = 4, + /** The universal tag type for Null */ + Null = 5, + /** The universal tag type for Object Identifier */ + ObjectIdentifier = 6, + /** The universal tag type for Object Descriptor */ + ObjectDescriptor = 7, + /** The universal tag type for External */ + External = 8, + /** The universal tag type for Real */ + Real = 9, + /** The universal tag type for Enumerated */ + Enumerated = 10, + /** The universal tag type for Embedded-PDV */ + EmbeddedPDV = 11, + /** The universal tag type for UTF8 String */ + UTF8String = 12, + /** The universal tag type for Relative Object Identifier */ + RelativeObjectIdentifier = 13, + /** The universal tag type for Time */ + Time = 14, + /** A reserved value */ + Reserved = 15, + /** The universal tag type Sequence */ + Sequence = 16, + /** The universal tag type for Set */ + Set = 17, + /** The universal tag type for Numeric String */ + NumericString = 18, + /** The universal tag type for Printable String */ + PrintableString = 19, + /** The universal tag type for T61String */ + T61String = 20, + /** The universal tag type for Videotex String */ + VideotexString = 21, + /** The universal tag type for IA5String */ + IA5String = 22, + /** The universal tag type for UTC time */ + UTCTime = 23, + /** The universal tag type for Generalized time */ + GeneralizedTime = 24, + /** The universal tag type for GraphicString */ + GraphicString = 25, + /** The universal tag type for VisibleString */ + VisibleString = 26, + /** The universal tag type for GeneralString */ + GeneralString = 27, + /** The universal tag type for UniversalString */ + UniversalString = 28, + /** The universal tag type for CharacterString */ + CharacterString = 29, + /** The universal tag type for BMPString */ + BMPString = 30, + /** The universal tag type for Date */ + Date = 31, + /** The universal tag type for Time of Day */ + TimeOfDay = 32, + /** The universal tag type for Date-Time */ + DateTime = 33, + /** The universal tag type for Duration */ + Duration = 34, + /** The universal tag type for Object Identifier Internationalized Resource Identifier (IRI) */ + ObjectIdentifierIRI = 35, + /** The universal tag type for Relative Object Identifier Internationalized Resource Identifier (IRI) */ + RelativeObjectIdentifierIRI = 36, + /** A non-applicable value */ + NotApplicable = 255 + }; + + /** + * @class Asn1Record + * Represents an ASN.1 record, as described in ITU-T Recommendation X.680: + * + * + * + */ + class Asn1Record + { + public: + /** + * A static method to decode a byte array into an Asn1Record + * @param data A byte array to decode + * @param dataLen The byte array length + * @param lazy Use lazy decoding, set to true by default. Lazy decoding entails delaying the decoding + * of the record value until it is accessed + * @return A smart pointer to the decoded ASN.1 record. If the byte stream is not a valid ASN.1 record + * an exception is thrown + */ + static std::unique_ptr decode(const uint8_t* data, size_t dataLen, bool lazy = true); + + /** + * Encode this record and convert it to a byte stream + * @return A vector of bytes representing the record + */ + std::vector encode(); + + /** + * @return The ASN.1 tag class + */ + Asn1TagClass getTagClass() const { return m_TagClass; } + + /** + * @return True if it's a constructed record, or false if it's a primitive record + */ + bool isConstructed() const { return m_IsConstructed; } + + /** + * @return The ASN.1 Universal tag type if the record is of class Universal, otherwise Asn1UniversalTagType#NotApplicable + */ + Asn1UniversalTagType getUniversalTagType() const; + + /** + * @return The ASN.1 tag type value + */ + uint8_t getTagType() const { return m_TagType; } + + /** + * @return The length of the record value + */ + size_t getValueLength() const { return m_ValueLength; } + + /** + * @return The total length of the record + */ + size_t getTotalLength() const { return m_TotalLength; } + + /** + * A templated method that accepts a class derived from Asn1Record as its template argument and attempts + * to cast the current instance to that type + * @tparam Asn1RecordType The type to cast to + * @return A pointer to the type after casting + */ + template + Asn1RecordType* castAs() + { + auto result = dynamic_cast(this); + if (result == nullptr) + { + throw std::bad_cast(); + } + return result; + } + + virtual ~Asn1Record() = default; + + protected: + Asn1TagClass m_TagClass = Asn1TagClass::Universal; + bool m_IsConstructed = false; + uint8_t m_TagType = 0; + + size_t m_ValueLength = 0; + size_t m_TotalLength = 0; + + uint8_t* m_EncodedValue = nullptr; + + Asn1Record() = default; + + static Asn1Record* decodeInternal(const uint8_t* data, size_t dataLen, bool lazy); + + virtual void decodeValue(uint8_t* data, bool lazy) = 0; + virtual std::vector encodeValue() const = 0; + + static Asn1Record* decodeTagAndCreateRecord(const uint8_t* data, size_t dataLen, int& tagLen); + int decodeLength(const uint8_t* data, size_t dataLen); + void decodeValueIfNeeded(); + + uint8_t encodeTag(); + std::vector encodeLength() const; + }; + + /** + * @class Asn1GenericRecord + * Represents a generic ASN.1 record, either of an unknown type or of a known type that doesn't + * have a dedicated parser yet + */ + class Asn1GenericRecord : public Asn1Record + { + friend class Asn1Record; + + public: + /** + * A constructor to create a generic record + * @param tagClass The record tag class + * @param isConstructed A flag to indicate if the record is constructed or primitive + * @param tagType The record tag type value + * @param value A byte array of the tag value + * @param valueLen The length of the value byte array + */ + Asn1GenericRecord(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const uint8_t* value, size_t valueLen); + + ~Asn1GenericRecord() override; + + /** + * @return A pointer to the tag value + */ + const uint8_t* getValue() { decodeValueIfNeeded(); return m_Value; } + + protected: + Asn1GenericRecord() = default; + + void decodeValue(uint8_t* data, bool lazy) override; + std::vector encodeValue() const override; + + private: + uint8_t* m_Value = nullptr; + bool m_FreeValueOnDestruction = false; + }; + + /** + * @class Asn1ConstructedRecord + * Represents a constructed ASN.1 record, which is a record that has sub-records + */ + class Asn1ConstructedRecord : public Asn1Record + { + friend class Asn1Record; + + public: + /** + * A constructor to create a constructed record + * @param tagClass The record tag class + * @param tagType The record tag type value + * @param subRecords A list of sub-records to assign as the record value + */ + explicit Asn1ConstructedRecord(Asn1TagClass tagClass, uint8_t tagType, const std::vector& subRecords); + + /** + * @return A reference to the list of sub-records. It's important to note that any modifications made to + * this list will directly affect the internal structure + */ + PointerVector& getSubRecords() { decodeValueIfNeeded(); return m_SubRecords; }; + + protected: + Asn1ConstructedRecord() = default; + + void decodeValue(uint8_t* data, bool lazy) override; + std::vector encodeValue() const override; + + private: + PointerVector m_SubRecords; + }; + + /** + * @class Asn1SequenceRecord + * Represents an ASN.1 record with a value of type Sequence + */ + class Asn1SequenceRecord : public Asn1ConstructedRecord + { + friend class Asn1Record; + + public: + /** + * A constructor to create a record of type Sequence + * @param subRecords A list of sub-records to assign as the record value + */ + explicit Asn1SequenceRecord(const std::vector& subRecords); + + private: + Asn1SequenceRecord() = default; + }; + + /** + * @class Asn1SetRecord + * Represents an ASN.1 record with a value of type Set + */ + class Asn1SetRecord : public Asn1ConstructedRecord + { + friend class Asn1Record; + + public: + /** + * A constructor to create a record of type Set + * @param subRecords A list of sub-records to assign as the record value + */ + explicit Asn1SetRecord(const std::vector& subRecords); + + private: + Asn1SetRecord() = default; + }; + + /** + * @class Asn1PrimitiveRecord + * Represents a primitive ASN.1 record, meaning a record that doesn't have sub-records. + * This is an abstract class that cannot be instantiated + */ + class Asn1PrimitiveRecord : public Asn1Record + { + friend class Asn1Record; + + protected: + Asn1PrimitiveRecord() = default; + explicit Asn1PrimitiveRecord(Asn1UniversalTagType tagType); + }; + + /** + * @class Asn1IntegerRecord + * Represents an ASN.1 record with a value of type Integer + */ + class Asn1IntegerRecord : public Asn1PrimitiveRecord + { + friend class Asn1Record; + + public: + /** + * A constructor to create a record of type Integer + * @param value An integer to set as the record value + */ + explicit Asn1IntegerRecord(uint32_t value); + + /** + * @return The integer value of this record + */ + uint32_t getValue() { decodeValueIfNeeded(); return m_Value; } + + protected: + Asn1IntegerRecord() = default; + + void decodeValue(uint8_t* data, bool lazy) override; + std::vector encodeValue() const override; + + private: + uint32_t m_Value = 0; + }; + + /** + * @class Asn1EnumeratedRecord + * Represents an ASN.1 record with a value of type Enumerated + */ + class Asn1EnumeratedRecord : public Asn1IntegerRecord + { + friend class Asn1Record; + + public: + /** + * A constructor to create a record of type Enumerated + * @param value An integer to set as the record value + */ + explicit Asn1EnumeratedRecord(uint32_t value); + + private: + Asn1EnumeratedRecord() = default; + }; + + /** + * @class Asn1OctetStringRecord + * Represents an ASN.1 record with a value of type Octet String + */ + class Asn1OctetStringRecord : public Asn1PrimitiveRecord + { + friend class Asn1Record; + + public: + /** + * A constructor to create a record of type Octet String + * @param value A string to set as the record value + */ + explicit Asn1OctetStringRecord(const std::string& value); + + /** + * @return The string value of this record + */ + std::string getValue() { decodeValueIfNeeded(); return m_Value; }; + + protected: + void decodeValue(uint8_t* data, bool lazy) override; + std::vector encodeValue() const override; + + private: + std::string m_Value; + + Asn1OctetStringRecord() = default; + }; + + /** + * @class Asn1BooleanRecord + * Represents an ASN.1 record with a value of type Boolean + */ + class Asn1BooleanRecord : public Asn1PrimitiveRecord + { + friend class Asn1Record; + + public: + /** + * A constructor to create a record of type Boolean + * @param value A boolean to set as the record value + */ + explicit Asn1BooleanRecord(bool value); + + /** + * @return The boolean value of this record + */ + bool getValue() { decodeValueIfNeeded(); return m_Value; }; + + protected: + void decodeValue(uint8_t* data, bool lazy) override; + std::vector encodeValue() const override; + + private: + Asn1BooleanRecord() = default; + + bool m_Value = false; + }; + + /** + * @class Asn1NullRecord + * Represents an ASN.1 record with a value of type Null + */ + class Asn1NullRecord : public Asn1PrimitiveRecord + { + friend class Asn1Record; + + public: + /** + * A constructor to create a record of type Null + */ + Asn1NullRecord(); + + protected: + void decodeValue(uint8_t* data, bool lazy) override {} + std::vector encodeValue() const override { return {}; } + }; +} diff --git a/Packet++/src/Asn1Codec.cpp b/Packet++/src/Asn1Codec.cpp new file mode 100644 index 0000000000..e0602a2a24 --- /dev/null +++ b/Packet++/src/Asn1Codec.cpp @@ -0,0 +1,574 @@ +#define LOG_MODULE PacketLogModuleAsn1Codec + +#include "Asn1Codec.h" +#include "GeneralUtils.h" +#include "EndianPortable.h" +#include +#include +#include +#include + +#if defined(_WIN32) +#undef max +#endif + +namespace pcpp { + std::unique_ptr Asn1Record::decode(const uint8_t* data, size_t dataLen, bool lazy) + { + auto record = decodeInternal(data, dataLen ,lazy); + return std::unique_ptr(record); + } + + uint8_t Asn1Record::encodeTag() + { + uint8_t tagByte; + + switch (m_TagClass) + { + case Asn1TagClass::Private: + { + tagByte = 0xc0; + break; + } + case Asn1TagClass::ContextSpecific: + { + tagByte = 0x80; + break; + } + case Asn1TagClass::Application: + { + tagByte = 0x40; + break; + } + default: + { + tagByte = 0; + break; + } + } + + if (m_IsConstructed) + { + tagByte |= 0x20; + } + + auto tagType = m_TagType & 0x1f; + tagByte |= tagType; + + return tagByte; + } + + std::vector Asn1Record::encodeLength() const + { + std::vector result; + + if (m_ValueLength < 128) + { + result.push_back(static_cast(m_ValueLength)); + return result; + } + + // Assuming the size is always 4 bytes + uint8_t firstByte = 0x80 | sizeof(uint32_t); + result.push_back(firstByte); + + result.push_back((m_ValueLength >> 24) & 0xff); + result.push_back((m_ValueLength >> 16) & 0xff); + result.push_back((m_ValueLength >> 8) & 0xff); + result.push_back(m_ValueLength & 0xff); + + return result; + } + + std::vector Asn1Record::encode() + { + std::vector result; + + result.push_back(encodeTag()); + + auto lengthBytes = encodeLength(); + result.insert(result.end(), lengthBytes.begin(), lengthBytes.end()); + + auto encodedValue = encodeValue(); + result.insert(result.end(), encodedValue.begin(), encodedValue.end()); + + return result; + } + + Asn1Record* Asn1Record::decodeInternal(const uint8_t* data, size_t dataLen, bool lazy) + { + int tagLen; + auto decodedRecord = decodeTagAndCreateRecord(data, dataLen, tagLen); + + int lengthLen; + try + { + lengthLen = decodedRecord->decodeLength(data + tagLen, dataLen - tagLen); + } + catch (...) + { + delete decodedRecord; + throw; + } + + if (static_cast(dataLen) - tagLen - lengthLen - static_cast(decodedRecord->m_ValueLength) < 0) + { + delete decodedRecord; + throw std::invalid_argument("Cannot decode ASN.1 record, data doesn't contain the entire record"); + } + + decodedRecord->m_TotalLength = tagLen + lengthLen + decodedRecord->m_ValueLength; + + if (!lazy) + { + try + { + decodedRecord->decodeValue(const_cast(data) + tagLen + lengthLen, lazy); + } + catch (...) + { + delete decodedRecord; + throw; + } + + } + else + { + decodedRecord->m_EncodedValue = const_cast(data) + tagLen + lengthLen; + } + + return decodedRecord; + } + + Asn1UniversalTagType Asn1Record::getUniversalTagType() const + { + if (m_TagClass == Asn1TagClass::Universal) + { + return static_cast(m_TagType); + } + + return Asn1UniversalTagType::NotApplicable; + } + + Asn1Record* Asn1Record::decodeTagAndCreateRecord(const uint8_t* data, size_t dataLen, int& tagLen) + { + if (dataLen < 1) + { + throw std::invalid_argument("Cannot decode ASN.1 record tag"); + } + + tagLen = 1; + + Asn1TagClass tagClass = Asn1TagClass::Universal; + + // Check first 2 bits + auto tagClassBits = data[0] & 0xc0; + if (tagClassBits == 0) + { + tagClass = Asn1TagClass::Universal; + } + else if ((tagClassBits & 0xc0) == 0xc0) + { + tagClass = Asn1TagClass::Private; + } + else if ((tagClassBits & 0x80) == 0x80) + { + tagClass = Asn1TagClass::ContextSpecific; + } + else if ((tagClassBits & 0x40) == 0x40) + { + tagClass = Asn1TagClass::Application; + } + + // Check bit 6 + auto tagTypeBits = data[0] & 0x20; + bool isConstructed = (tagTypeBits != 0); + + // Check last 5 bits + auto tagType = data[0] & 0x1f; + if (tagType == 0x1f) + { + if (dataLen < 2) + { + throw std::invalid_argument("Cannot decode ASN.1 record tag"); + } + + if ((data[1] & 0x80) != 0) + { + throw std::invalid_argument("ASN.1 tags with value larger than 127 are not supported"); + } + + tagType = data[1] & 0x7f; + tagLen = 2; + } + + Asn1Record* newRecord; + + if (isConstructed) + { + if (tagClass == Asn1TagClass::Universal) + { + switch (static_cast(tagType)) + { + case Asn1UniversalTagType::Sequence: + { + newRecord = new Asn1SequenceRecord(); + break; + } + case Asn1UniversalTagType::Set: + { + newRecord = new Asn1SetRecord(); + break; + } + default: + { + newRecord = new Asn1ConstructedRecord(); + } + } + } + else + { + newRecord = new Asn1ConstructedRecord(); + } + } + else + { + if (tagClass == Asn1TagClass::Universal) + { + auto asn1UniversalTagType = static_cast(tagType); + switch (asn1UniversalTagType) + { + case Asn1UniversalTagType::Integer: + { + newRecord = new Asn1IntegerRecord(); + break; + } + case Asn1UniversalTagType::Enumerated: + { + newRecord = new Asn1EnumeratedRecord(); + break; + } + case Asn1UniversalTagType::OctetString: + { + newRecord = new Asn1OctetStringRecord(); + break; + } + case Asn1UniversalTagType::Boolean: + { + newRecord = new Asn1BooleanRecord(); + break; + } + case Asn1UniversalTagType::Null: + { + newRecord = new Asn1NullRecord(); + break; + } + default: + { + newRecord = new Asn1GenericRecord(); + } + } + } + else + { + newRecord = new Asn1GenericRecord(); + } + } + + newRecord->m_TagClass = tagClass; + newRecord->m_IsConstructed = isConstructed; + newRecord->m_TagType = tagType; + + return newRecord; + } + + int Asn1Record::decodeLength(const uint8_t* data, size_t dataLen) + { + if (dataLen < 1) + { + throw std::invalid_argument("Cannot decode ASN.1 record length"); + } + + // Check 8th bit + auto lengthForm = data[0] & 0x80; + + auto numberLengthBytes = 1; + + // Check if the tag is using more than one byte + // 8th bit at 0 means the length only uses one byte + // 8th bit at 1 means the length uses more than one byte. The number of bytes is encoded in the other 7 bits + if (lengthForm != 0) + { + auto additionalLengthBytes = data[0] & 0x7F; + if (static_cast(dataLen) < additionalLengthBytes + 1) + { + throw std::invalid_argument("Cannot decode ASN.1 record length"); + } + for (auto index = additionalLengthBytes; index > 0; --index) + { + m_ValueLength += data[index] * static_cast(std::pow(256, (additionalLengthBytes - index))); + } + numberLengthBytes += additionalLengthBytes; + } + else + { + m_ValueLength = data[0]; + } + + return numberLengthBytes; + } + + void Asn1Record::decodeValueIfNeeded() + { + if (m_EncodedValue != nullptr) + { + decodeValue(m_EncodedValue, true); + m_EncodedValue = nullptr; + } + } + + Asn1GenericRecord::Asn1GenericRecord(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const uint8_t* value, size_t valueLen) + { + m_TagType = tagType; + m_TagClass = tagClass; + m_IsConstructed = isConstructed; + m_Value = new uint8_t[valueLen]; + m_FreeValueOnDestruction = true; + memcpy(m_Value, value, valueLen); + m_ValueLength = valueLen; + m_TotalLength = m_ValueLength + 2; + } + + Asn1GenericRecord::~Asn1GenericRecord() + { + if (m_Value && m_FreeValueOnDestruction) + { + delete m_Value; + } + } + + void Asn1GenericRecord::decodeValue(uint8_t* data, bool lazy) + { + m_Value = data; + } + + std::vector Asn1GenericRecord::encodeValue() const + { + return {m_Value, m_Value + m_ValueLength}; + } + + Asn1ConstructedRecord::Asn1ConstructedRecord(Asn1TagClass tagClass, uint8_t tagType, const std::vector& subRecords) + { + m_TagType = tagType; + m_TagClass = tagClass; + m_IsConstructed = true; + + size_t recordValueLength = 0; + for (auto record : subRecords) + { + auto encodedRecord = record->encode(); + auto copyRecord = Asn1Record::decode(encodedRecord.data(), encodedRecord.size(), false); + m_SubRecords.pushBack(copyRecord.release()); + recordValueLength += encodedRecord.size(); + } + + m_ValueLength = recordValueLength; + m_TotalLength = recordValueLength + 2; + } + + void Asn1ConstructedRecord::decodeValue(uint8_t* data, bool lazy) + { + if (!(data || m_ValueLength)) + { + return; + } + + auto value = data; + auto valueLen = m_ValueLength; + + while (valueLen > 0) + { + auto subRecord = Asn1Record::decodeInternal(value, valueLen, lazy); + value += subRecord->getTotalLength(); + valueLen -= subRecord->getTotalLength(); + + m_SubRecords.pushBack(subRecord); + } + } + + std::vector Asn1ConstructedRecord::encodeValue() const + { + std::vector result; + result.reserve(m_ValueLength); + + for (auto record : m_SubRecords) + { + auto encodedRecord = record->encode(); + result.insert(result.end(), std::make_move_iterator(encodedRecord.begin()), std::make_move_iterator(encodedRecord.end())); + } + return result; + } + + Asn1SequenceRecord::Asn1SequenceRecord(const std::vector& subRecords) + : Asn1ConstructedRecord(Asn1TagClass::Universal, static_cast(Asn1UniversalTagType::Sequence), subRecords) + {} + + Asn1SetRecord::Asn1SetRecord(const std::vector& subRecords) + : Asn1ConstructedRecord(Asn1TagClass::Universal, static_cast(Asn1UniversalTagType::Set), subRecords) + {} + + Asn1PrimitiveRecord::Asn1PrimitiveRecord(Asn1UniversalTagType tagType) : Asn1Record() + { + m_TagType = static_cast(tagType); + m_TagClass = Asn1TagClass::Universal; + m_IsConstructed = false; + } + + Asn1IntegerRecord::Asn1IntegerRecord(uint32_t value) : Asn1PrimitiveRecord(Asn1UniversalTagType::Integer) + { + m_Value = value; + + if (m_Value <= std::numeric_limits::max()) + { + m_ValueLength = sizeof(uint8_t); + } + else if (value <= std::numeric_limits::max()) + { + m_ValueLength = sizeof(uint16_t); + } + else if (value <= std::pow(2, 3 * 8)) + { + m_ValueLength = 3; + } + else + { + m_ValueLength = sizeof(uint32_t); + } + + m_TotalLength = m_ValueLength + 2; + } + + void Asn1IntegerRecord::decodeValue(uint8_t* data, bool lazy) + { + switch (m_ValueLength) + { + case 1: + { + m_Value = *data; + break; + } + case 2: + { + m_Value = be16toh(*reinterpret_cast(data)); + break; + } + case 3: + { + uint8_t tempData[4] = {0}; + memcpy(tempData + 1, data, 3); + m_Value = be32toh(*reinterpret_cast(tempData)); + break; + } + case 4: + { + m_Value = be32toh(*reinterpret_cast(data)); + break; + } + default: + { + throw std::runtime_error("An integer ASN.1 record of more than 4 bytes is not supported"); + } + } + } + + std::vector Asn1IntegerRecord::encodeValue() const + { + std::vector result; + result.reserve(m_ValueLength); + + switch (m_ValueLength) + { + case 1: + { + result.push_back(static_cast(m_Value)); + break; + } + case 2: + { + uint8_t tempArr[sizeof(uint16_t)]; + auto hostValue = htobe16(static_cast(m_Value)); + memcpy(tempArr, &hostValue, m_ValueLength); + std::copy(tempArr, tempArr + m_ValueLength, std::back_inserter(result)); + break; + } + case 3: + { + uint8_t tempArr[sizeof(uint32_t)]; + auto hostValue = htobe32(static_cast(m_Value)); + memcpy(tempArr, &hostValue, m_ValueLength + 1); + std::copy(tempArr + 1, tempArr + m_ValueLength + 1, std::back_inserter(result)); + break; + } + case 4: + { + uint8_t tempArr[sizeof(uint32_t)]; + auto hostValue = htobe32(static_cast(m_Value)); + memcpy(tempArr, &hostValue, m_ValueLength); + std::copy(tempArr, tempArr + m_ValueLength, std::back_inserter(result)); + break; + } + default: + { + throw std::runtime_error("Integer value of more than 4 bytes is not supported"); + } + } + + return result; + } + + Asn1EnumeratedRecord::Asn1EnumeratedRecord(uint32_t value) : Asn1IntegerRecord(value) + { + m_TagType = static_cast(Asn1UniversalTagType::Enumerated); + } + + Asn1OctetStringRecord::Asn1OctetStringRecord(const std::string& value) : Asn1PrimitiveRecord(Asn1UniversalTagType::OctetString) + { + m_Value = value; + m_ValueLength = value.size(); + m_TotalLength = m_ValueLength + 2; + } + + void Asn1OctetStringRecord::decodeValue(uint8_t* data, bool lazy) + { + m_Value = std::string(reinterpret_cast(data), m_ValueLength); + } + + std::vector Asn1OctetStringRecord::encodeValue() const + { + return {m_Value.begin(), m_Value.end()}; + } + + Asn1BooleanRecord::Asn1BooleanRecord(bool value) : Asn1PrimitiveRecord(Asn1UniversalTagType::Boolean) + { + m_Value = value; + m_ValueLength = 1; + m_TotalLength = 3; + } + + void Asn1BooleanRecord::decodeValue(uint8_t* data, bool lazy) + { + m_Value = data[0] != 0; + } + + std::vector Asn1BooleanRecord::encodeValue() const + { + uint8_t byte = (m_Value ? 0xff : 0x00); + return { byte }; + } + + Asn1NullRecord::Asn1NullRecord() : Asn1PrimitiveRecord(Asn1UniversalTagType::Null) + { + m_ValueLength = 0; + m_TotalLength = 2; + } +} diff --git a/README.md b/README.md index c2e144ef47..86c78d3f70 100644 --- a/README.md +++ b/README.md @@ -249,20 +249,21 @@ PcapPlusPlus currently supports parsing, editing and creation of packets of the ### Application Layer (L7) -34. BGP (v4) -35. DHCP -36. DHCPv6 -37. DNS -38. FTP -39. HTTP headers (request & response) -40. NTP (v3, v4) -41. Radius -42. S7 Communication (S7comm) -43. SMTP -44. SOME/IP -45. SSH - parsing only (no editing capabilities) -46. Telnet - parsing only (no editing capabilities) -47. Generic payload +34. ASN.1 decoder and encoder +35. BGP (v4) +36. DHCP +37. DHCPv6 +38. DNS +39. FTP +40. HTTP headers (request & response) +41. NTP (v3, v4) +42. Radius +43. S7 Communication (S7comm) +44. SMTP +45. SOME/IP +46. SSH - parsing only (no editing capabilities) +47. Telnet - parsing only (no editing capabilities) +48. Generic payload ## DPDK And PF_RING Support diff --git a/Tests/Packet++Test/CMakeLists.txt b/Tests/Packet++Test/CMakeLists.txt index ca628f539b..8776d170a6 100644 --- a/Tests/Packet++Test/CMakeLists.txt +++ b/Tests/Packet++Test/CMakeLists.txt @@ -1,6 +1,7 @@ add_executable( Packet++Test main.cpp + Tests/Asn1Tests.cpp Tests/BgpTests.cpp Tests/CotpTests.cpp Tests/DhcpTests.cpp diff --git a/Tests/Packet++Test/TestDefinition.h b/Tests/Packet++Test/TestDefinition.h index 56471aa341..baa566accd 100644 --- a/Tests/Packet++Test/TestDefinition.h +++ b/Tests/Packet++Test/TestDefinition.h @@ -254,3 +254,7 @@ PTF_TEST_CASE(S7CommLayerCreationTest); PTF_TEST_CASE(SmtpParsingTests); PTF_TEST_CASE(SmtpCreationTests); PTF_TEST_CASE(SmtpEditTests); + +// Implemented in Asn1Tests.cpp +PTF_TEST_CASE(Asn1DecodingTest); +PTF_TEST_CASE(Asn1EncodingTest); diff --git a/Tests/Packet++Test/Tests/Asn1Tests.cpp b/Tests/Packet++Test/Tests/Asn1Tests.cpp new file mode 100644 index 0000000000..3aa46c4a73 --- /dev/null +++ b/Tests/Packet++Test/Tests/Asn1Tests.cpp @@ -0,0 +1,524 @@ +#include "../TestDefinition.h" +#include "Asn1Codec.h" +#include "RawPacket.h" +#include "GeneralUtils.h" +#include +#include + +PTF_TEST_CASE(Asn1DecodingTest) +{ + // Context specific + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("870b6f626a656374636c617373", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::ContextSpecific, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::NotApplicable, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 13); + PTF_ASSERT_EQUAL(record->getValueLength(), 11); + auto genericRecord = record->castAs(); + auto recordValue = std::string(genericRecord->getValue(), genericRecord->getValue() + genericRecord->getValueLength()); + PTF_ASSERT_EQUAL(recordValue, "objectclass"); + } + + // Integer 1 byte + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("020106", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Integer, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 3); + PTF_ASSERT_EQUAL(record->getValueLength(), 1); + PTF_ASSERT_EQUAL(record->castAs()->getValue(), 6); + } + + // Integer 2 bytes + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("020203e8", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Integer, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 4); + PTF_ASSERT_EQUAL(record->getValueLength(), 2); + PTF_ASSERT_EQUAL(record->castAs()->getValue(), 1000); + } + + // Integer 3 bytes + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("02030186a0", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Integer, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 5); + PTF_ASSERT_EQUAL(record->getValueLength(), 3); + PTF_ASSERT_EQUAL(record->castAs()->getValue(), 100000); + } + + // Integer 4 bytes + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("020400989680", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Integer, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 6); + PTF_ASSERT_EQUAL(record->getValueLength(), 4); + PTF_ASSERT_EQUAL(record->castAs()->getValue(), 10000000); + } + + // Integer more than 4 bytes + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("020502540be400", data, 20); + PTF_ASSERT_RAISES(pcpp::Asn1Record::decode(data, dataLen, false), std::runtime_error, "An integer ASN.1 record of more than 4 bytes is not supported"); + } + + // Enumerated + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("0a022000", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Enumerated, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 4); + PTF_ASSERT_EQUAL(record->getValueLength(), 2); + PTF_ASSERT_EQUAL(record->castAs()->getValue(), 8192); + } + + // Boolean - true + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("0101ff", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Boolean, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 3); + PTF_ASSERT_EQUAL(record->getValueLength(), 1); + PTF_ASSERT_TRUE(record->castAs()->getValue()); + } + + // Boolean - false + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("010100", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Boolean, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 3); + PTF_ASSERT_EQUAL(record->getValueLength(), 1); + PTF_ASSERT_FALSE(record->castAs()->getValue()); + } + + // OctetString + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("0411737562736368656d61537562656e747279", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::OctetString, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 19); + PTF_ASSERT_EQUAL(record->getValueLength(), 17); + PTF_ASSERT_EQUAL(record->castAs()->getValue(), "subschemaSubentry"); + } + + // Null + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("0500", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Null, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 2); + PTF_ASSERT_EQUAL(record->getValueLength(), 0); + PTF_ASSERT_NOT_NULL(record->castAs()); + } + + // Sequence + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("300a040461626364020203e8", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_TRUE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Sequence, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 12); + PTF_ASSERT_EQUAL(record->getValueLength(), 10); + + auto& subRecords = record->castAs()->getSubRecords(); + PTF_ASSERT_EQUAL(subRecords.size(), 2); + PTF_ASSERT_EQUAL(subRecords.at(0)->castAs()->getValue(), "abcd"); + PTF_ASSERT_EQUAL(subRecords.at(1)->castAs()->getValue(), 1000); + } + + // Set + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("310a020203e8040461626364", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_TRUE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::Set, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 12); + PTF_ASSERT_EQUAL(record->getValueLength(), 10); + + auto& subRecords = record->castAs()->getSubRecords(); + PTF_ASSERT_EQUAL(subRecords.size(), 2); + PTF_ASSERT_EQUAL(subRecords.at(0)->castAs()->getValue(), 1000); + PTF_ASSERT_EQUAL(subRecords.at(1)->castAs()->getValue(), "abcd"); + } + + // Application constructed + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("630a040461626364020203e8", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Application, enumclass); + PTF_ASSERT_TRUE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::NotApplicable, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 12); + PTF_ASSERT_EQUAL(record->getValueLength(), 10); + + auto& subRecords = record->castAs()->getSubRecords(); + PTF_ASSERT_EQUAL(subRecords.size(), 2); + PTF_ASSERT_EQUAL(subRecords.at(0)->castAs()->getValue(), "abcd"); + PTF_ASSERT_EQUAL(subRecords.at(1)->castAs()->getValue(), 1000); + } + + // Tag > 30 + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("1f23076d7976616c7565", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getUniversalTagType(), pcpp::Asn1UniversalTagType::ObjectIdentifierIRI, enumclass); + PTF_ASSERT_EQUAL(record->getTotalLength(), 10); + PTF_ASSERT_EQUAL(record->getValueLength(), 7); + auto genericRecord = record->castAs(); + auto recordValue = std::string(genericRecord->getValue(), genericRecord->getValue() + genericRecord->getValueLength()); + PTF_ASSERT_EQUAL(recordValue, "myvalue"); + } + + // Unknown tag + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("1f28076d7976616c7565", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + + PTF_ASSERT_EQUAL(record->getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record->isConstructed()); + PTF_ASSERT_EQUAL(record->getTagType(), 40); + PTF_ASSERT_EQUAL(record->getTotalLength(), 10); + PTF_ASSERT_EQUAL(record->getValueLength(), 7); + auto genericRecord = record->castAs(); + auto recordValue = std::string(genericRecord->getValue(), genericRecord->getValue() + genericRecord->getValueLength()); + PTF_ASSERT_EQUAL(recordValue, "myvalue"); + } + + // Tag > 127 + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("1f8100076d7976616c7565", data, 20); + PTF_ASSERT_RAISES(pcpp::Asn1Record::decode(data, dataLen), std::invalid_argument, "ASN.1 tags with value larger than 127 are not supported"); + } + + // Not enough data to parse tag + { + uint8_t data[20]; + pcpp::hexStringToByteArray("1f8100076d7976616c7565", data, 20); + PTF_ASSERT_RAISES(pcpp::Asn1Record::decode(data, 0), std::invalid_argument, "Cannot decode ASN.1 record tag"); + PTF_ASSERT_RAISES(pcpp::Asn1Record::decode(data, 1), std::invalid_argument, "Cannot decode ASN.1 record tag"); + } + + // Not enough data to parse length + { + uint8_t data[20]; + pcpp::hexStringToByteArray("0500", data, 20); + PTF_ASSERT_RAISES(pcpp::Asn1Record::decode(data, 1), std::invalid_argument, "Cannot decode ASN.1 record length"); + } + + // Incomplete record - doesn't contain the entire value + { + uint8_t data[20]; + pcpp::hexStringToByteArray("0a022000", data, 20); + PTF_ASSERT_RAISES(pcpp::Asn1Record::decode(data, 3), std::invalid_argument, "Cannot decode ASN.1 record, data doesn't contain the entire record"); + } + + // Cast as the wrong type + { + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("0a022000", data, 20); + auto record = pcpp::Asn1Record::decode(data, dataLen); + #ifdef _MSC_VER + auto expectedMessage = "bad cast"; + #else + auto expectedMessage = "std::bad_cast"; + #endif + PTF_ASSERT_RAISES(record->castAs(), std::bad_cast, expectedMessage); + } +}; // Asn1DecodingTest + + +PTF_TEST_CASE(Asn1EncodingTest) +{ + // Generic record + { + uint8_t value[] = {0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x63, 0x6c, 0x61, 0x73, 0x73}; + pcpp::Asn1GenericRecord record(pcpp::Asn1TagClass::ContextSpecific, false, 7, value, 11); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::ContextSpecific, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::NotApplicable, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 13); + PTF_ASSERT_EQUAL(record.getValueLength(), 11); + auto recordValue = std::string(record.getValue(), record.getValue() + record.getValueLength()); + PTF_ASSERT_EQUAL(recordValue, "objectclass"); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("870b6f626a656374636c617373", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // Integer 1 byte + { + pcpp::Asn1IntegerRecord record(6); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::Integer, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 3); + PTF_ASSERT_EQUAL(record.getValueLength(), 1); + PTF_ASSERT_EQUAL(record.getValue(), 6); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("020106", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // Integer 2 bytes + { + pcpp::Asn1IntegerRecord record(1000); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::Integer, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 4); + PTF_ASSERT_EQUAL(record.getValueLength(), 2); + PTF_ASSERT_EQUAL(record.getValue(), 1000); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("020203e8", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // Integer 3 bytes + { + pcpp::Asn1IntegerRecord record(100000); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::Integer, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 5); + PTF_ASSERT_EQUAL(record.getValueLength(), 3); + PTF_ASSERT_EQUAL(record.getValue(), 100000); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("02030186a0", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // Integer 4 bytes + { + pcpp::Asn1IntegerRecord record(100000000); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::Integer, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 6); + PTF_ASSERT_EQUAL(record.getValueLength(), 4); + PTF_ASSERT_EQUAL(record.getValue(), 100000000); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("020405f5e100", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // Enumerated + { + pcpp::Asn1EnumeratedRecord record(8192); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::Enumerated, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 4); + PTF_ASSERT_EQUAL(record.getValueLength(), 2); + PTF_ASSERT_EQUAL(record.getValue(), 8192); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("0a022000", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // OctetString + { + pcpp::Asn1OctetStringRecord record("subschemaSubentry"); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::OctetString, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 19); + PTF_ASSERT_EQUAL(record.getValueLength(), 17); + PTF_ASSERT_EQUAL(record.getValue(), "subschemaSubentry"); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("0411737562736368656d61537562656e747279", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // Boolean - true + { + pcpp::Asn1BooleanRecord record(true); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::Boolean, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 3); + PTF_ASSERT_EQUAL(record.getValueLength(), 1); + PTF_ASSERT_TRUE(record.getValue()); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("0101ff", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // Boolean - false + { + pcpp::Asn1BooleanRecord record(false); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("010100", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // Null + { + pcpp::Asn1NullRecord record; + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::Null, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 2); + PTF_ASSERT_EQUAL(record.getValueLength(), 0); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("0500", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + + // Sequence + { + pcpp::Asn1OctetStringRecord octestStringRecord("abcd"); + pcpp::Asn1IntegerRecord integerRecord(1000); + pcpp::Asn1SequenceRecord record({ &octestStringRecord, &integerRecord}); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_TRUE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::Sequence, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 12); + PTF_ASSERT_EQUAL(record.getValueLength(), 10); + + auto& subRecords = record.getSubRecords(); + PTF_ASSERT_EQUAL(subRecords.size(), 2); + PTF_ASSERT_EQUAL(subRecords.at(0)->castAs()->getValue(), "abcd"); + PTF_ASSERT_EQUAL(subRecords.at(1)->castAs()->getValue(), 1000); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("300a040461626364020203e8", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen); + } + + // Set + { + pcpp::Asn1OctetStringRecord octestStringRecord("abcd"); + pcpp::Asn1IntegerRecord integerRecord(1000); + pcpp::Asn1SetRecord record({ &integerRecord, &octestStringRecord }); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::Universal, enumclass); + PTF_ASSERT_TRUE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::Set, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 12); + PTF_ASSERT_EQUAL(record.getValueLength(), 10); + + auto& subRecords = record.getSubRecords(); + PTF_ASSERT_EQUAL(subRecords.size(), 2); + PTF_ASSERT_EQUAL(subRecords.at(0)->castAs()->getValue(), 1000); + PTF_ASSERT_EQUAL(subRecords.at(1)->castAs()->getValue(), "abcd"); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("310a020203e8040461626364", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen); + } +} // Asn1EncodingTest diff --git a/Tests/Packet++Test/main.cpp b/Tests/Packet++Test/main.cpp index 1138a235a6..0aee7ae37d 100644 --- a/Tests/Packet++Test/main.cpp +++ b/Tests/Packet++Test/main.cpp @@ -325,5 +325,8 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(SmtpCreationTests, "smtp"); PTF_RUN_TEST(SmtpEditTests, "smtp"); + PTF_RUN_TEST(Asn1DecodingTest, "asn1"); + PTF_RUN_TEST(Asn1EncodingTest, "asn1"); + PTF_END_RUNNING_TESTS; }