#include #include #include #include #include #include #include #include #include #include using namespace Botan; #include #include #include // Set this if your terminal understands UTF-8; otherwise output is in Latin-1 #define UTF8_TERMINAL 1 /* What level the outermost layer of stuff is at. Probably 0 or 1; asn1parse uses 0 as the outermost, while 1 makes more sense to me. 2+ doesn't make much sense at all. */ #define INITIAL_LEVEL 0 void decode(BER_Decoder&, size_t); void emit(const std::string&, size_t, size_t, const std::string& = ""); std::string type_name(ASN1_Tag); std::string url_encode(const std::vector& in) { std::ostringstream out; for(size_t i = 0; i != in.size(); ++i) { const int c = in[i]; if(isprint((int)c)) out << (char)c; else out << "%" << std::hex << (int)c << std::dec; } return out.str(); } int main(int argc, char* argv[]) { if(argc != 2) { printf("Usage: %s \n", argv[0]); return 1; } LibraryInitializer init; try { DataSource_Stream in(argv[1]); if(!PEM_Code::matches(in)) { BER_Decoder decoder(in); decode(decoder, INITIAL_LEVEL); } else { std::string label; // ignored BER_Decoder decoder(PEM_Code::decode(in, label)); decode(decoder, INITIAL_LEVEL); } } catch(std::exception& e) { printf("%s\n", e.what()); return 1; } return 0; } void decode(BER_Decoder& decoder, size_t level) { 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 bits = encoder.get_contents_unlocked(); BER_Decoder data(bits); if(class_tag & CONSTRUCTED) { BER_Decoder cons_info(obj.value); if(type_tag == SEQUENCE) { emit("SEQUENCE", level, length); decode(cons_info, level+1); } else if(type_tag == SET) { emit("SET", level, length); decode(cons_info, level+1); } 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 = type_name(type_tag) + " (cons)"; emit(name, level, length); decode(cons_info, level+1); } } else if((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC)) { #if 0 std::vector bits; data.decode(bits, type_tag); try { BER_Decoder inner(bits); decode(inner, level + 1); } catch(...) { emit("[" + std::to_string(type_tag) + "]", level, length, url_encode(bits)); } #else emit("[" + std::to_string(type_tag) + "]", level, length, url_encode(bits)); #endif } else if(type_tag == OBJECT_ID) { OID oid; data.decode(oid); std::string out = OIDS::lookup(oid); if(out != oid.as_string()) out += " [" + oid.as_string() + "]"; emit(type_name(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 rep; /* If it's small, it's probably a number, not a hash */ if(number.bits() <= 16) 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 += (char)rep[i]; emit(type_name(type_tag), level, length, str); } else if(type_tag == BOOLEAN) { bool boolean; data.decode(boolean); emit(type_name(type_tag), level, length, (boolean ? "true" : "false")); } else if(type_tag == NULL_TAG) { emit(type_name(type_tag), level, length); } else if(type_tag == OCTET_STRING) { std::vector bits; data.decode(bits, type_tag); try { BER_Decoder inner(bits); decode(inner, level + 1); } catch(...) { emit(type_name(type_tag), level, length, url_encode(bits)); } } else if(type_tag == BIT_STRING) { std::vector bits; data.decode(bits, type_tag); std::vector bit_set; for(size_t i = 0; i != bits.size(); ++i) for(size_t j = 0; j != 8; ++j) bit_set.push_back((bool)((bits[bits.size()-i-1] >> (7-j)) & 1)); std::string bit_str; for(size_t i = 0; i != bit_set.size(); ++i) { bool the_bit = bit_set[bit_set.size()-i-1]; if(!the_bit && bit_str.size() == 0) continue; bit_str += (the_bit ? "1" : "0"); } emit(type_name(type_tag), level, length, bit_str); } else if(type_tag == PRINTABLE_STRING || type_tag == NUMERIC_STRING || type_tag == IA5_STRING || type_tag == T61_STRING || type_tag == VISIBLE_STRING || type_tag == UTF8_STRING || type_tag == BMP_STRING) { ASN1_String str; data.decode(str); if(UTF8_TERMINAL) emit(type_name(type_tag), level, length, Charset::transcode(str.iso_8859(), LATIN1_CHARSET, UTF8_CHARSET)); else emit(type_name(type_tag), level, length, str.iso_8859()); } else if(type_tag == UTC_TIME || type_tag == GENERALIZED_TIME) { X509_Time time; data.decode(time); emit(type_name(type_tag), level, length, time.readable_string()); } else fprintf(stderr, "Unknown tag: class=%02X, type=%02X\n", class_tag, type_tag); obj = decoder.get_next_object(); } } void emit(const std::string& type, size_t level, size_t length, const std::string& value) { const size_t LIMIT = 128; const size_t BIN_LIMIT = 64; int written = 0; written += printf(" d=%2d, l=%4d: ", (int)level, (int)length); for(size_t i = INITIAL_LEVEL; i != level; ++i) written += printf(" "); written += printf("%s ", type.c_str()); bool should_skip = false; if(value.length() > LIMIT) should_skip = true; if((type == "OCTET STRING" || type == "BIT STRING") && value.length() > BIN_LIMIT) should_skip = true; if(value != "" && !should_skip) { if(written % 2 == 0) printf(" "); while(written < 50) written += printf(" "); printf(":%s\n", value.c_str()); } else printf("\n"); } std::string type_name(ASN1_Tag type) { if(type == PRINTABLE_STRING) return "PRINTABLE STRING"; if(type == NUMERIC_STRING) return "NUMERIC STRING"; if(type == IA5_STRING) return "IA5 STRING"; if(type == T61_STRING) return "T61 STRING"; if(type == UTF8_STRING) return "UTF8 STRING"; if(type == VISIBLE_STRING) return "VISIBLE STRING"; if(type == BMP_STRING) return "BMP STRING"; if(type == UTC_TIME) return "UTC TIME"; if(type == GENERALIZED_TIME) return "GENERALIZED TIME"; if(type == OCTET_STRING) return "OCTET STRING"; if(type == BIT_STRING) return "BIT STRING"; if(type == ENUMERATED) return "ENUMERATED"; if(type == INTEGER) return "INTEGER"; if(type == NULL_TAG) return "NULL"; if(type == OBJECT_ID) return "OBJECT"; if(type == BOOLEAN) return "BOOLEAN"; return "(UNKNOWN)"; }