diff options
-rw-r--r-- | src/fuzzer/asn1.cpp | 21 | ||||
-rw-r--r-- | src/lib/asn1/asn1_obj.cpp | 6 | ||||
-rw-r--r-- | src/lib/asn1/asn1_print.cpp | 248 | ||||
-rw-r--r-- | src/lib/asn1/asn1_print.h | 86 |
4 files changed, 216 insertions, 145 deletions
diff --git a/src/fuzzer/asn1.cpp b/src/fuzzer/asn1.cpp index 8fabad5ed..da3e1607f 100644 --- a/src/fuzzer/asn1.cpp +++ b/src/fuzzer/asn1.cpp @@ -8,6 +8,25 @@ #include <botan/asn1_print.h> #include <fstream> +class ASN1_Parser final : public Botan::ASN1_Formatter + { + public: + ASN1_Parser() : Botan::ASN1_Formatter(true) {} + + protected: + std::string format(Botan::ASN1_Tag, Botan::ASN1_Tag, size_t, size_t, + const std::string&) const override + { + return ""; + } + + std::string format_bin(Botan::ASN1_Tag, Botan::ASN1_Tag, + const std::vector<uint8_t>&) const override + { + return ""; + } + }; + void fuzz(const uint8_t in[], size_t len) { try @@ -17,7 +36,7 @@ void fuzz(const uint8_t in[], size_t len) * on actual output formatting, no memory is allocated, etc. */ std::ofstream out; - Botan::ASN1_Pretty_Printer printer; + ASN1_Parser printer; printer.print_to_stream(out, in, len); } catch(Botan::Exception& e) { } diff --git a/src/lib/asn1/asn1_obj.cpp b/src/lib/asn1/asn1_obj.cpp index 1f93a4b8b..c83875ae5 100644 --- a/src/lib/asn1/asn1_obj.cpp +++ b/src/lib/asn1/asn1_obj.cpp @@ -16,6 +16,12 @@ std::string asn1_tag_to_string(ASN1_Tag type) { switch(type) { + case Botan::SEQUENCE: + return "SEQUENCE"; + + case Botan::SET: + return "SET"; + case Botan::PRINTABLE_STRING: return "PRINTABLE STRING"; diff --git a/src/lib/asn1/asn1_print.cpp b/src/lib/asn1/asn1_print.cpp index 979d041d8..ffb2eda48 100644 --- a/src/lib/asn1/asn1_print.cpp +++ b/src/lib/asn1/asn1_print.cpp @@ -18,89 +18,24 @@ namespace Botan { -std::string ASN1_Pretty_Printer::print(const uint8_t in[], size_t len) const +std::string ASN1_Formatter::print(const uint8_t in[], size_t len) const { std::ostringstream output; print_to_stream(output, in, len); return output.str(); } -void ASN1_Pretty_Printer::print_to_stream(std::ostream& output, - const uint8_t in[], - size_t len) const +void ASN1_Formatter::print_to_stream(std::ostream& output, + const uint8_t in[], + size_t len) const { BER_Decoder dec(in, len); - decode(output, dec, m_initial_level); + decode(output, dec, 0); } -std::string ASN1_Pretty_Printer::format_binary(const std::vector<uint8_t>& in) const - { - std::ostringstream out; - - size_t unprintable = 0; - - for(size_t i = 0; i != in.size(); ++i) - { - const int c = in[i]; - if(std::isalnum(c)) - { - out << static_cast<char>(c); - } - else - { - out << "x" << std::hex << static_cast<int>(c) << std::dec; - ++unprintable; - if(unprintable >= in.size() / 4) - { - return hex_encode(in); - } - } - } - - return out.str(); - } - -void ASN1_Pretty_Printer::emit(std::ostream& output, - const std::string& type, - size_t level, size_t length, - const std::string& value) const - { - std::ostringstream oss; - - oss << " d=" << std::setw(2) << level - << ", l=" << std::setw(4) << length << ":" - << std::string(level + 1, ' ') << type; - - bool should_skip = false; - - if(value.length() > m_print_limit) - { - should_skip = true; - } - - if((type == "OCTET STRING" || type == "BIT STRING") && value.length() > m_print_binary_limit) - { - should_skip = true; - } - - const std::string s = oss.str(); - - output << s; - - if(value != "" && !should_skip) - { - const size_t spaces_to_align = - (s.size() >= m_value_column) ? 1 : (m_value_column - s.size()); - - output << std::string(spaces_to_align, ' ') << value; - } - - output << "\n"; - } - -void ASN1_Pretty_Printer::decode(std::ostream& output, - BER_Decoder& decoder, - size_t level) const +void ASN1_Formatter::decode(std::ostream& output, + BER_Decoder& decoder, + size_t level) const { BER_Object obj = decoder.get_next_object(); @@ -114,48 +49,15 @@ void ASN1_Pretty_Printer::decode(std::ostream& output, that we've gotten the type info */ DER_Encoder encoder; encoder.add_object(type_tag, class_tag, obj.value); - std::vector<uint8_t> bits = encoder.get_contents_unlocked(); + const std::vector<uint8_t> bits = encoder.get_contents_unlocked(); BER_Decoder data(bits); if(class_tag & CONSTRUCTED) { BER_Decoder cons_info(obj.value); - if(type_tag == SEQUENCE) - { - emit(output, "SEQUENCE", level, length); - decode(output, cons_info, level + 1); // recurse - } - else if(type_tag == SET) - { - emit(output, "SET", level, length); - decode(output, cons_info, level + 1); // recurse - } - else - { - std::string name; - - if((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC)) - { - name = "cons [" + std::to_string(type_tag) + "]"; - - if(class_tag & APPLICATION) - { - name += " appl"; - } - if(class_tag & CONTEXT_SPECIFIC) - { - name += " context"; - } - } - else - { - name = asn1_tag_to_string(type_tag) + " (cons)"; - } - - emit(output, name, level, length); - decode(output, cons_info, level + 1); // recurse - } + output << format(type_tag, class_tag, level, length, ""); + decode(output, cons_info, level + 1); // recurse } else if((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC)) { @@ -166,16 +68,21 @@ void ASN1_Pretty_Printer::decode(std::ostream& output, std::vector<uint8_t> inner_bits; data.decode(inner_bits, type_tag); BER_Decoder inner(inner_bits); - decode(output, inner, level + 1); // recurse + + std::ostringstream inner_data; + decode(inner_data, inner, level + 1); // recurse + output << inner_data.str(); } catch(...) { - emit(output, "[" + std::to_string(type_tag) + "]", level, length, format_binary(bits)); + output << format(type_tag, class_tag, level, length, + format_bin(type_tag, class_tag, bits)); } } else { - emit(output, "[" + std::to_string(type_tag) + "]", level, length, format_binary(bits)); + output << format(type_tag, class_tag, level, length, + format_bin(type_tag, class_tag, bits)); } } else if(type_tag == OBJECT_ID) @@ -193,7 +100,7 @@ void ASN1_Pretty_Printer::decode(std::ostream& output, out += " [" + oid.as_string() + "]"; } - emit(output, asn1_tag_to_string(type_tag), level, length, out); + output << format(type_tag, class_tag, level, length, out); } else if(type_tag == INTEGER || type_tag == ENUMERATED) { @@ -216,17 +123,17 @@ void ASN1_Pretty_Printer::decode(std::ostream& output, str += static_cast<char>(rep[i]); } - emit(output, asn1_tag_to_string(type_tag), level, length, str); + output << format(type_tag, class_tag, level, length, str); } else if(type_tag == BOOLEAN) { bool boolean; data.decode(boolean); - emit(output, asn1_tag_to_string(type_tag), level, length, (boolean ? "true" : "false")); + output << format(type_tag, class_tag, level, length, (boolean ? "true" : "false")); } else if(type_tag == NULL_TAG) { - emit(output, asn1_tag_to_string(type_tag), level, length); + output << format(type_tag, class_tag, level, length, ""); } else if(type_tag == OCTET_STRING || type_tag == BIT_STRING) { @@ -238,27 +145,28 @@ void ASN1_Pretty_Printer::decode(std::ostream& output, BER_Decoder inner(decoded_bits); std::ostringstream inner_data; - decode(inner_data, inner, level + 1); + decode(inner_data, inner, level + 1); // recurse - emit(output, asn1_tag_to_string(type_tag), level, length, ""); + output << format(type_tag, class_tag, level, length, ""); output << inner_data.str(); } catch(...) { - emit(output, asn1_tag_to_string(type_tag), level, length, format_binary(decoded_bits)); + output << format(type_tag, class_tag, level, length, + format_bin(type_tag, class_tag, decoded_bits)); } } else if(ASN1_String::is_string_type(type_tag)) { ASN1_String str; data.decode(str); - emit(output, asn1_tag_to_string(type_tag), level, length, str.value()); + output << format(type_tag, class_tag, level, length, str.value()); } else if(type_tag == UTC_TIME || type_tag == GENERALIZED_TIME) { X509_Time time; data.decode(time); - emit(output, asn1_tag_to_string(type_tag), level, length, time.readable_string()); + output << format(type_tag, class_tag, level, length, time.readable_string()); } else { @@ -270,4 +178,102 @@ void ASN1_Pretty_Printer::decode(std::ostream& output, } } +namespace { + +std::string format_type(ASN1_Tag type_tag, ASN1_Tag class_tag) + { + if((class_tag & CONSTRUCTED) && ((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC))) + { + std::string name = "cons [" + std::to_string(type_tag) + "]"; + + if(class_tag & APPLICATION) + { + name += " appl"; + } + if(class_tag & CONTEXT_SPECIFIC) + { + name += " context"; + } + + return name; + } + else + { + return asn1_tag_to_string(type_tag); + } + } + +} + +std::string ASN1_Pretty_Printer::format(ASN1_Tag type_tag, + ASN1_Tag class_tag, + size_t level, + size_t length, + const std::string& value) const + { + bool should_skip = false; + + if(value.length() > m_print_limit) + { + should_skip = true; + } + + if((type_tag == OCTET_STRING || type_tag == BIT_STRING) && + value.length() > m_print_binary_limit) + { + should_skip = true; + } + + level += m_initial_level; + + std::ostringstream oss; + + oss << " d=" << std::setw(2) << level + << ", l=" << std::setw(4) << length << ":" + << std::string(level + 1, ' ') << format_type(type_tag, class_tag); + + if(value != "" && !should_skip) + { + const size_t current_pos = static_cast<size_t>(oss.tellp()); + const size_t spaces_to_align = + (current_pos >= m_value_column) ? 1 : (m_value_column - current_pos); + + oss << std::string(spaces_to_align, ' ') << value; + } + + oss << "\n"; + + return oss.str(); + } + +std::string ASN1_Pretty_Printer::format_bin(ASN1_Tag /*type_tag*/, + ASN1_Tag /*class_tag*/, + const std::vector<uint8_t>& vec) const + { + const size_t unprintable_bound = vec.size() / 4; + size_t unprintable = 0; + + std::ostringstream out; + + for(size_t i = 0; i != vec.size(); ++i) + { + const int c = vec[i]; + if(std::isalnum(c)) + { + out << static_cast<char>(c); + } + else + { + out << "x" << std::hex << static_cast<int>(c) << std::dec; + ++unprintable; + if(unprintable >= unprintable_bound) + { + return hex_encode(vec); + } + } + } + + return out.str(); + } + } diff --git a/src/lib/asn1/asn1_print.h b/src/lib/asn1/asn1_print.h index 0fd760e89..4f9fa8f4d 100644 --- a/src/lib/asn1/asn1_print.h +++ b/src/lib/asn1/asn1_print.h @@ -7,7 +7,7 @@ #ifndef BOTAN_ASN1_PRINT_H_ #define BOTAN_ASN1_PRINT_H_ -#include <botan/types.h> +#include <botan/asn1_obj.h> #include <string> #include <vector> #include <iosfwd> @@ -17,27 +17,17 @@ namespace Botan { class BER_Decoder; /** -* Format ASN.1 data into human readable strings +* Format ASN.1 data and call a virtual to format */ -class BOTAN_DLL ASN1_Pretty_Printer +class BOTAN_DLL ASN1_Formatter { public: + virtual ~ASN1_Formatter() = default; + /** - * @param print_limit strings larger than this are not printed - * @param print_binary_limit binary strings larger than this are not printed * @param print_context_specific if true, try to parse nested context specific data. - * @param initial_level the initial depth (0 or 1 are the only reasonable values) - * @param value_column ASN.1 values are lined up at this column in output */ - ASN1_Pretty_Printer(size_t print_limit = 256, - size_t print_binary_limit = 256, - bool print_context_specific = true, - size_t initial_level = 0, - size_t value_column = 60) : - m_print_limit(print_limit), - m_print_binary_limit(print_binary_limit), - m_initial_level(initial_level), - m_value_column(value_column), + ASN1_Formatter(bool print_context_specific) : m_print_context_specific(print_context_specific) {} @@ -53,23 +43,73 @@ class BOTAN_DLL ASN1_Pretty_Printer return print(vec.data(), vec.size()); } - private: - void emit(std::ostream& out, - const std::string& type, - size_t level, size_t length, - const std::string& value = "") const; + protected: + /** + * This is called for each element + */ + virtual std::string format(ASN1_Tag type_tag, + ASN1_Tag class_tag, + size_t level, + size_t length, + const std::string& value) const = 0; + + /** + * This is called to format binary elements that we don't know how to + * convert to a string The result will be passed as value to format; the + * tags are included as a hint to aid decoding. + */ + virtual std::string format_bin(ASN1_Tag type_tag, + ASN1_Tag class_tag, + const std::vector<uint8_t>& vec) const = 0; + private: void decode(std::ostream& output, BER_Decoder& decoder, size_t level) const; - std::string format_binary(const std::vector<uint8_t>& in) const; + const bool m_print_context_specific; + }; + +/** +* Format ASN.1 data into human readable strings +*/ +class BOTAN_DLL ASN1_Pretty_Printer final : public ASN1_Formatter + { + public: + /** + * @param print_limit strings larger than this are not printed + * @param print_binary_limit binary strings larger than this are not printed + * @param print_context_specific if true, try to parse nested context specific data. + * @param initial_level the initial depth (0 or 1 are the only reasonable values) + * @param value_column ASN.1 values are lined up at this column in output + */ + ASN1_Pretty_Printer(size_t print_limit = 256, + size_t print_binary_limit = 256, + bool print_context_specific = true, + size_t initial_level = 0, + size_t value_column = 60) : + ASN1_Formatter(print_context_specific), + m_print_limit(print_limit), + m_print_binary_limit(print_binary_limit), + m_initial_level(initial_level), + m_value_column(value_column) + {} + + private: + std::string format(ASN1_Tag type_tag, + ASN1_Tag class_tag, + size_t level, + size_t length, + const std::string& value) const override; + + std::string format_bin(ASN1_Tag type_tag, + ASN1_Tag class_tag, + const std::vector<uint8_t>& vec) const override; const size_t m_print_limit; const size_t m_print_binary_limit; const size_t m_initial_level; const size_t m_value_column; - const bool m_print_context_specific; }; } |