parse encapsulated structures (octet string or bit string containing valid ASN.1): first dirty version v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Fri, 16 Jul 2021 21:01:02 +0200
branchv_0
changeset 31 a87c97aecbf6
parent 30 e27e133731ee
child 32 00d76921c547
parse encapsulated structures (octet string or bit string containing valid ASN.1): first dirty version
src/lib/BasicASN1Reader.h
src/lib/GenericASN1ContentHandler.h
--- a/src/lib/BasicASN1Reader.h	Sun Jul 11 18:09:19 2021 +0200
+++ b/src/lib/BasicASN1Reader.h	Fri Jul 16 21:01:02 2021 +0200
@@ -171,20 +171,22 @@
 			std::string s;
 			s.resize(typeHeader.length);
 			read((uint8_t*) s.data(), typeHeader.length);
-			handlers.writeOctetString(typeHeader, s);
+			if (processEncapsulatedContent(typeHeader, s) == false) handlers.writeOctetString(typeHeader, s);
 		} else if (typeHeader.tag == UniversalType::BitString && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
 			// TODO: check available bytes before allocating buffer
 			std::string s;
 			s.resize(typeHeader.length);
 			read((uint8_t*) s.data(), typeHeader.length);
-			std::vector<bool> bits;
-			// TODO: throw exception on wrong padding or insufficient length?
-			if (s.size() > 1) {
-				uint8_t padding = s[0];
-				for (uint8_t j = padding; j < 8; j++) bits.push_back(s.back() & 1 << j);
-				for (size_t i = s.size() - 2; i > 0; i--) for (uint8_t j = 0; j < 8; j++) bits.push_back(s[i] & 1 << j);
+			if (processEncapsulatedContent(typeHeader, s) == false) {
+				std::vector<bool> bits;
+				// TODO: throw exception on wrong padding or insufficient length?
+				if (s.size() > 1) {
+					uint8_t padding = s[0];
+					for (uint8_t j = padding; j < 8; j++) bits.push_back(s.back() & 1 << j);
+					for (size_t i = s.size() - 2; i > 0; i--) for (uint8_t j = 0; j < 8; j++) bits.push_back(s[i] & 1 << j);
+				}
+				handlers.writeBitString(typeHeader, bits);
 			}
-			handlers.writeBitString(typeHeader, bits);
 		} else if (typeHeader.tag == UniversalType::UTCTime && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
 			// TODO: check available bytes before allocating buffer
 			// TODO: check encoding
@@ -249,6 +251,7 @@
 
 		} else {
 			// TODO: do not skip, parse
+			// TODO: check available bytes before allocating buffer
 			std::vector<uint8_t> temp(typeHeader.length, 0);
 			read(temp.data(), typeHeader.length);
 			// TODO: recover transaction?
@@ -261,6 +264,7 @@
 					<< " length = " << typeHeader.length
 					<< " definite = " << (typeHeader.definiteLength ? "true" : "false");
 
+			// TODO: special event for unparsed? (instead of a text string)
 			handlers.writeTextString(typeHeader, description.str());
 		}
 
@@ -278,6 +282,56 @@
 		}
 	}
 
+	bool isValidBER(const std::string& input) {
+		BasicASN1Reader encapsulatedReader;
+		try {
+			encapsulatedReader.write((const uint8_t*) input.c_str(), input.size());
+			encapsulatedReader.close();
+			return true;
+		} catch (...) {
+			return false;
+		}
+	}
+
+	class EncapsulatedASN1ContentHandler : public ASN1ContentHandlerProxy {
+	public:
+
+		void writeStreamStart() override {
+			// skip this event
+		}
+
+		void writeStreamEnd() override {
+			// skip this event
+		}
+	};
+
+	/**
+	 * @param typeHeader
+	 * @param input OCTET STRING or BIT STRING raw bytes
+	 * @return whether we found valid content and passed parsed results to handlers
+	 */
+	bool processEncapsulatedContent(const BasicHeader& typeHeader, const std::string& input) {
+		// TODO: avoid double parsing + encapsulated content might be also processed at the XML/DOM level where we may even do conditional processing based on XPath (evaluate only certain octet- or bit- strings)
+		// We may also do the same as with SEQUENCE or SET (continue nested reading in this ASN1Rreader instance), but it would require valid encapsulated data and would avoid easy fallback to raw OCTET or BIT STRING. We would also have to check the boundaries of the nested part.
+		if (isValidBER(input)) {
+			handlers.writeCollectionStart(typeHeader);
+
+			BasicASN1Reader encapsulatedReader;
+			std::shared_ptr<EncapsulatedASN1ContentHandler> encapsulatedHandler = std::make_shared<EncapsulatedASN1ContentHandler>();
+			encapsulatedHandler->addHandler(std::shared_ptr<ASN1ContentHandlerProxy>(&handlers, [](auto doNotDeleteHere) {
+			})); // FIXME: correct memory management
+			encapsulatedReader.addHandler(encapsulatedHandler);
+
+			encapsulatedReader.write((const uint8_t*) input.c_str(), input.size());
+			encapsulatedReader.close();
+
+			handlers.writeCollectionEnd();
+			return true;
+		} else {
+			return false;
+		}
+	}
+
 protected:
 
 	void update() override {
@@ -288,6 +342,8 @@
 
 	void close() override {
 		if (hasAvailableForReading()) throw std::logic_error("Unexpected content at the end of the stream"); // TODO: better exception
+		
+		// TODO: check also open sequences etc.; maybe in the handler
 
 		checkRemainingItems();
 		// TODO: check the bytes remaining in the buffer
--- a/src/lib/GenericASN1ContentHandler.h	Sun Jul 11 18:09:19 2021 +0200
+++ b/src/lib/GenericASN1ContentHandler.h	Fri Jul 16 21:01:02 2021 +0200
@@ -55,6 +55,7 @@
 	void writeCollectionStart(const Header& header) override {
 		if (header.tag == UniversalType::Sequence) handlers.writeStartElement("sequence");
 		else if (header.tag == UniversalType::Set) handlers.writeStartElement("set");
+		else if (header.tag == UniversalType::OctetString || header.tag == UniversalType::BitString) handlers.writeStartElement("encapsulated",{"type", std::to_string(header.tag)}); // TODO: type name, better attributes
 		else handlers.writeStartElement("constructed");
 	}