diff options
author | Jack Lloyd <[email protected]> | 2017-11-15 16:08:57 -0500 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2017-11-15 16:50:53 -0500 |
commit | a94a462f6ddab82bae2e42ca19aa9d7e5780a5b0 (patch) | |
tree | 91e6d3eba5d5ed37eb6bf7e6e9407ce4c73b3517 | |
parent | e2217197ac766d11a28ee1389e414ca8bbb0b294 (diff) |
Move ASN1 printer to the library
-rw-r--r-- | src/cli/asn1.cpp | 385 | ||||
-rw-r--r-- | src/lib/asn1/asn1_obj.cpp | 57 | ||||
-rw-r--r-- | src/lib/asn1/asn1_obj.h | 2 | ||||
-rw-r--r-- | src/lib/asn1/asn1_print.cpp | 279 | ||||
-rw-r--r-- | src/lib/asn1/asn1_print.h | 77 |
5 files changed, 418 insertions, 382 deletions
diff --git a/src/cli/asn1.cpp b/src/cli/asn1.cpp index 13bcb94a9..6460d5587 100644 --- a/src/cli/asn1.cpp +++ b/src/cli/asn1.cpp @@ -8,390 +8,11 @@ #if defined(BOTAN_HAS_ASN1) && defined(BOTAN_HAS_PEM_CODEC) -#include <botan/bigint.h> -#include <botan/hex.h> -#include <botan/der_enc.h> -#include <botan/ber_dec.h> -#include <botan/asn1_time.h> -#include <botan/asn1_str.h> -#include <botan/oids.h> #include <botan/pem.h> - -#include <iomanip> -#include <sstream> -#include <cctype> +#include <botan/asn1_print.h> namespace Botan_CLI { -namespace { - -std::string type_name(Botan::ASN1_Tag type) - { - switch(type) - { - case Botan::PRINTABLE_STRING: - return "PRINTABLE STRING"; - - case Botan::NUMERIC_STRING: - return "NUMERIC STRING"; - - case Botan::IA5_STRING: - return "IA5 STRING"; - - case Botan::T61_STRING: - return "T61 STRING"; - - case Botan::UTF8_STRING: - return "UTF8 STRING"; - - case Botan::VISIBLE_STRING: - return "VISIBLE STRING"; - - case Botan::BMP_STRING: - return "BMP STRING"; - - case Botan::UTC_TIME: - return "UTC TIME"; - - case Botan::GENERALIZED_TIME: - return "GENERALIZED TIME"; - - case Botan::OCTET_STRING: - return "OCTET STRING"; - - case Botan::BIT_STRING: - return "BIT STRING"; - - case Botan::ENUMERATED: - return "ENUMERATED"; - - case Botan::INTEGER: - return "INTEGER"; - - case Botan::NULL_TAG: - return "NULL"; - - case Botan::OBJECT_ID: - return "OBJECT"; - - case Botan::BOOLEAN: - return "BOOLEAN"; - - default: - return "TAG(" + std::to_string(static_cast<size_t>(type)) + ")"; - } - } - -class ASN1_Pretty_Printer - { - public: - 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 = 50) : - m_print_limit(print_limit), - m_print_binary_limit(print_binary_limit), - m_initial_level(initial_level), - m_value_column(value_column), - m_print_context_specific(print_context_specific) - {} - - void print_to_stream(std::ostream& out, - const uint8_t in[], - size_t len) const; - - std::string print(const uint8_t in[], size_t len) const; - - template<typename Alloc> - std::string print(const std::vector<uint8_t, Alloc>& vec) const - { - 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; - - void decode(std::ostream& output, - Botan::BER_Decoder& decoder, - size_t level) const; - - std::string format_binary(const std::vector<uint8_t>& in) const; - - 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; - }; - -std::string ASN1_Pretty_Printer::print(const uint8_t in[], size_t len) const - { - std::ostringstream out; - print_to_stream(out, in, len); - return out.str(); - } - -void ASN1_Pretty_Printer::print_to_stream(std::ostream& out, - const uint8_t in[], - size_t len) const - { - Botan::BER_Decoder dec(in, len); - this->decode(out, dec, m_initial_level); - } - -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 << "%" << std::hex << static_cast<int>(c) << std::dec; - ++unprintable; - if(unprintable >= in.size() / 4) - { - return Botan::hex_encode(in); - } - } - } - - return out.str(); - } - -void ASN1_Pretty_Printer::emit(std::ostream& out, - const std::string& type, - size_t level, size_t length, - const std::string& value) const - { - std::streampos starting_pos = out.tellp(); - - out << " d=" << std::setw(2) << level - << ", l=" << std::setw(4) << length << ": "; - - for(size_t i = 0; i != level; ++i) - { - out << ' '; - } - - out << 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; - } - - if(value != "" && !should_skip) - { - while(static_cast<size_t>(out.tellp() - starting_pos) < m_value_column) - { - out << ' '; - } - - out << value; - } - - out << "\n"; - } - -void ASN1_Pretty_Printer::decode(std::ostream& output, - Botan::BER_Decoder& decoder, - size_t level) const - { - Botan::BER_Object obj = decoder.get_next_object(); - - while(obj.type_tag != Botan::NO_OBJECT) - { - const Botan::ASN1_Tag type_tag = obj.type_tag; - const Botan::ASN1_Tag class_tag = obj.class_tag; - const size_t length = obj.value.size(); - - /* hack to insert the tag+length back in front of the stuff now - that we've gotten the type info */ - Botan::DER_Encoder encoder; - encoder.add_object(type_tag, class_tag, obj.value); - std::vector<uint8_t> bits = encoder.get_contents_unlocked(); - - Botan::BER_Decoder data(bits); - - if(class_tag & Botan::CONSTRUCTED) - { - Botan::BER_Decoder cons_info(obj.value); - if(type_tag == Botan::SEQUENCE) - { - emit(output, "SEQUENCE", level, length); - decode(output, cons_info, level + 1); // recurse - } - else if(type_tag == Botan::SET) - { - emit(output, "SET", level, length); - decode(output, cons_info, level + 1); // recurse - } - else - { - std::string name; - - if((class_tag & Botan::APPLICATION) || (class_tag & Botan::CONTEXT_SPECIFIC)) - { - name = "cons [" + std::to_string(type_tag) + "]"; - - if(class_tag & Botan::APPLICATION) - { - name += " appl"; - } - if(class_tag & Botan::CONTEXT_SPECIFIC) - { - name += " context"; - } - } - else - { - name = type_name(type_tag) + " (cons)"; - } - - emit(output, name, level, length); - decode(output, cons_info, level + 1); // recurse - } - } - else if((class_tag & Botan::APPLICATION) || (class_tag & Botan::CONTEXT_SPECIFIC)) - { - if(m_print_context_specific) - { - std::vector<uint8_t> bits; - data.decode(bits, type_tag); - - try - { - Botan::BER_Decoder inner(bits); - decode(output, inner, level + 1); // recurse - } - catch(...) - { - emit(output, "[" + std::to_string(type_tag) + "]", level, length, format_binary(bits)); - } - } - else - { - emit(output, "[" + std::to_string(type_tag) + "]", level, length, format_binary(bits)); - } - } - else if(type_tag == Botan::OBJECT_ID) - { - Botan::OID oid; - data.decode(oid); - - std::string out = Botan::OIDS::lookup(oid); - if(out.empty()) - { - out = oid.as_string(); - } - else - { - out += " [" + oid.as_string() + "]"; - } - - emit(output, type_name(type_tag), level, length, out); - } - else if(type_tag == Botan::INTEGER || type_tag == Botan::ENUMERATED) - { - Botan::BigInt number; - - if(type_tag == Botan::INTEGER) - { - data.decode(number); - } - else if(type_tag == Botan::ENUMERATED) - { - data.decode(number, Botan::ENUMERATED, class_tag); - } - - std::vector<uint8_t> rep; - - /* If it's small, it's probably a number, not a hash */ - if(number.bits() <= 20) - { - rep = Botan::BigInt::encode(number, Botan::BigInt::Decimal); - } - else - { - rep = Botan::BigInt::encode(number, Botan::BigInt::Hexadecimal); - } - - std::string str; - for(size_t i = 0; i != rep.size(); ++i) - { - str += static_cast<char>(rep[i]); - } - - emit(output, type_name(type_tag), level, length, str); - } - else if(type_tag == Botan::BOOLEAN) - { - bool boolean; - data.decode(boolean); - emit(output, type_name(type_tag), level, length, (boolean ? "true" : "false")); - } - else if(type_tag == Botan::NULL_TAG) - { - emit(output, type_name(type_tag), level, length); - } - else if(type_tag == Botan::OCTET_STRING || type_tag == Botan::BIT_STRING) - { - std::vector<uint8_t> decoded_bits; - data.decode(decoded_bits, type_tag); - - try - { - Botan::BER_Decoder inner(decoded_bits); - decode(output, inner, level + 1); - } - catch(...) - { - emit(output, type_name(type_tag), level, length, format_binary(decoded_bits)); - } - } - else if(Botan::ASN1_String::is_string_type(type_tag)) - { - Botan::ASN1_String str; - data.decode(str); - emit(output, type_name(type_tag), level, length, str.value()); - } - else if(type_tag == Botan::UTC_TIME || type_tag == Botan::GENERALIZED_TIME) - { - Botan::X509_Time time; - data.decode(time); - emit(output, type_name(type_tag), level, length, time.readable_string()); - } - else - { - output << "Unknown ASN.1 tag class=" << static_cast<int>(class_tag) - << " type=" << static_cast<int>(type_tag) << "\n";; - } - - obj = decoder.get_next_object(); - } - } - -} - class ASN1_Printer final : public Command { public: @@ -418,8 +39,8 @@ class ASN1_Printer final : public Command const size_t BIN_LIMIT = 1024; const bool PRINT_CONTEXT_SPECIFIC = true; - ASN1_Pretty_Printer printer(LIMIT, BIN_LIMIT, PRINT_CONTEXT_SPECIFIC); - output() << printer.print(contents); + Botan::ASN1_Pretty_Printer printer(LIMIT, BIN_LIMIT, PRINT_CONTEXT_SPECIFIC); + printer.print_to_stream(output(), contents.data(), contents.size()); } }; diff --git a/src/lib/asn1/asn1_obj.cpp b/src/lib/asn1/asn1_obj.cpp index 2dc73738c..1f93a4b8b 100644 --- a/src/lib/asn1/asn1_obj.cpp +++ b/src/lib/asn1/asn1_obj.cpp @@ -12,6 +12,63 @@ namespace Botan { +std::string asn1_tag_to_string(ASN1_Tag type) + { + switch(type) + { + case Botan::PRINTABLE_STRING: + return "PRINTABLE STRING"; + + case Botan::NUMERIC_STRING: + return "NUMERIC STRING"; + + case Botan::IA5_STRING: + return "IA5 STRING"; + + case Botan::T61_STRING: + return "T61 STRING"; + + case Botan::UTF8_STRING: + return "UTF8 STRING"; + + case Botan::VISIBLE_STRING: + return "VISIBLE STRING"; + + case Botan::BMP_STRING: + return "BMP STRING"; + + case Botan::UTC_TIME: + return "UTC TIME"; + + case Botan::GENERALIZED_TIME: + return "GENERALIZED TIME"; + + case Botan::OCTET_STRING: + return "OCTET STRING"; + + case Botan::BIT_STRING: + return "BIT STRING"; + + case Botan::ENUMERATED: + return "ENUMERATED"; + + case Botan::INTEGER: + return "INTEGER"; + + case Botan::NULL_TAG: + return "NULL"; + + case Botan::OBJECT_ID: + return "OBJECT"; + + case Botan::BOOLEAN: + return "BOOLEAN"; + + default: + return "TAG(" + std::to_string(static_cast<size_t>(type)) + ")"; + } + } + /* * BER Decoding Exceptions */ diff --git a/src/lib/asn1/asn1_obj.h b/src/lib/asn1/asn1_obj.h index da065ff6d..a785a5807 100644 --- a/src/lib/asn1/asn1_obj.h +++ b/src/lib/asn1/asn1_obj.h @@ -56,6 +56,8 @@ enum ASN1_Tag : uint32_t { DIRECTORY_STRING = 0xFF01 }; +std::string BOTAN_DLL asn1_tag_to_string(ASN1_Tag type); + /** * Basic ASN.1 Object Interface */ diff --git a/src/lib/asn1/asn1_print.cpp b/src/lib/asn1/asn1_print.cpp new file mode 100644 index 000000000..e314b5c99 --- /dev/null +++ b/src/lib/asn1/asn1_print.cpp @@ -0,0 +1,279 @@ +/* +* (C) 2014,2015,2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/asn1_print.h> +#include <botan/bigint.h> +#include <botan/hex.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/asn1_time.h> +#include <botan/asn1_str.h> +#include <botan/oids.h> +#include <iomanip> +#include <sstream> +#include <cctype> + +namespace Botan { + +std::string ASN1_Pretty_Printer::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 + { + BER_Decoder dec(in, len); + decode(output, dec, m_initial_level); + } + +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 << "%" << 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 + { + BER_Object obj = decoder.get_next_object(); + + while(obj.type_tag != NO_OBJECT) + { + const ASN1_Tag type_tag = obj.type_tag; + const ASN1_Tag class_tag = obj.class_tag; + const size_t length = obj.value.size(); + + /* hack to insert the tag+length back in front of the stuff now + 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(); + + 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 + } + } + else if((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC)) + { + if(m_print_context_specific) + { + std::vector<uint8_t> bits; + data.decode(bits, type_tag); + + try + { + BER_Decoder inner(bits); + decode(output, inner, level + 1); // recurse + } + catch(...) + { + emit(output, "[" + std::to_string(type_tag) + "]", level, length, format_binary(bits)); + } + } + else + { + emit(output, "[" + std::to_string(type_tag) + "]", level, length, format_binary(bits)); + } + } + else if(type_tag == OBJECT_ID) + { + OID oid; + data.decode(oid); + + std::string out = OIDS::lookup(oid); + if(out.empty()) + { + out = oid.as_string(); + } + else + { + out += " [" + oid.as_string() + "]"; + } + + emit(output, asn1_tag_to_string(type_tag), level, length, out); + } + else if(type_tag == INTEGER || type_tag == ENUMERATED) + { + BigInt number; + + if(type_tag == INTEGER) + { + data.decode(number); + } + else if(type_tag == ENUMERATED) + { + data.decode(number, ENUMERATED, class_tag); + } + + std::vector<uint8_t> rep; + + /* If it's small, it's probably a number, not a hash */ + if(number.bits() <= 20) + { + rep = BigInt::encode(number, BigInt::Decimal); + } + else + { + rep = BigInt::encode(number, BigInt::Hexadecimal); + } + + std::string str; + for(size_t i = 0; i != rep.size(); ++i) + { + str += static_cast<char>(rep[i]); + } + + emit(output, asn1_tag_to_string(type_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")); + } + else if(type_tag == NULL_TAG) + { + emit(output, asn1_tag_to_string(type_tag), level, length); + } + else if(type_tag == OCTET_STRING || type_tag == BIT_STRING) + { + std::vector<uint8_t> decoded_bits; + data.decode(decoded_bits, type_tag); + + try + { + BER_Decoder inner(decoded_bits); + decode(output, inner, level + 1); + } + catch(...) + { + emit(output, asn1_tag_to_string(type_tag), level, length, format_binary(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()); + } + 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()); + } + else + { + output << "Unknown ASN.1 tag class=" << static_cast<int>(class_tag) + << " type=" << static_cast<int>(type_tag) << "\n";; + } + + obj = decoder.get_next_object(); + } + } + +} diff --git a/src/lib/asn1/asn1_print.h b/src/lib/asn1/asn1_print.h new file mode 100644 index 000000000..0fd760e89 --- /dev/null +++ b/src/lib/asn1/asn1_print.h @@ -0,0 +1,77 @@ +/* +* (C) 2014,2015,2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_ASN1_PRINT_H_ +#define BOTAN_ASN1_PRINT_H_ + +#include <botan/types.h> +#include <string> +#include <vector> +#include <iosfwd> + +namespace Botan { + +class BER_Decoder; + +/** +* Format ASN.1 data into human readable strings +*/ +class BOTAN_DLL ASN1_Pretty_Printer + { + 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) : + m_print_limit(print_limit), + m_print_binary_limit(print_binary_limit), + m_initial_level(initial_level), + m_value_column(value_column), + m_print_context_specific(print_context_specific) + {} + + void print_to_stream(std::ostream& out, + const uint8_t in[], + size_t len) const; + + std::string print(const uint8_t in[], size_t len) const; + + template<typename Alloc> + std::string print(const std::vector<uint8_t, Alloc>& vec) const + { + 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; + + void decode(std::ostream& output, + BER_Decoder& decoder, + size_t level) const; + + std::string format_binary(const std::vector<uint8_t>& in) const; + + 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; + }; + +} + +#endif |