diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c0232777..8fe1786b8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,8 @@ target_include_directories(neopg-tool PUBLIC ${CLI11_INCLUDE_DIR} ${RANG_INCLUDE_DIR} ${SPDLOG_INCLUDE_DIR} + ${JSON_INCLUDE_DIR} + ${SPDLOG_INCLUDE_DIR} ../include ) diff --git a/src/cli/packet_command.cpp b/src/cli/packet_command.cpp index abdf1af29..4c0eb9e4b 100644 --- a/src/cli/packet_command.cpp +++ b/src/cli/packet_command.cpp @@ -9,9 +9,15 @@ #include #include +#include +#include +#include #include +#include #include #include +#include +#include #include #include @@ -20,8 +26,45 @@ #include #include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include +#include + +#include +#include #include @@ -29,6 +72,8 @@ #include +#include + #include namespace NeoPG { @@ -44,6 +89,394 @@ void UserIdPacketCommand::run() { packet.write(std::cout); } +static void output_public_key_data(PublicKeyData* pub) { + PublicKeyMaterial* key = nullptr; + switch (pub->version()) { + case PublicKeyVersion::V2: + case PublicKeyVersion::V3: { + auto v3pub = dynamic_cast(pub); + std::cout << "\tversion " << static_cast(pub->version()) << ", algo " + << static_cast(v3pub->m_algorithm) << ", created " + << v3pub->m_created << ", expires " << v3pub->m_days_valid + << "\n"; + key = v3pub->m_key.get(); + } break; + case PublicKeyVersion::V4: { + auto v4pub = dynamic_cast(pub); + std::cout << "\tversion " << static_cast(pub->version()) << ", algo " + << static_cast(v4pub->m_algorithm) << ", created " + << v4pub->m_created << ", expires 0" + << "\n"; + key = v4pub->m_key.get(); + } break; + default: + std::cout << "\tversion " << static_cast(pub->version()) << "\n"; + break; + } + if (key) { + switch (key->algorithm()) { + case PublicKeyAlgorithm::Rsa: { + auto rsa = dynamic_cast(key); + std::cout << "\tpkey[0]: [" << rsa->m_n.length() << " bits]\n"; + std::cout << "\tpkey[1]: [" << rsa->m_e.length() << " bits]\n"; + } break; + case PublicKeyAlgorithm::Dsa: { + auto dsa = dynamic_cast(key); + std::cout << "\tpkey[0]: [" << dsa->m_p.length() << " bits]\n"; + std::cout << "\tpkey[1]: [" << dsa->m_q.length() << " bits]\n"; + std::cout << "\tpkey[2]: [" << dsa->m_g.length() << " bits]\n"; + std::cout << "\tpkey[3]: [" << dsa->m_y.length() << " bits]\n"; + } break; + case PublicKeyAlgorithm::Elgamal: { + auto elgamal = dynamic_cast(key); + std::cout << "\tpkey[0]: [" << elgamal->m_p.length() << " bits]\n"; + std::cout << "\tpkey[1]: [" << elgamal->m_g.length() << " bits]\n"; + std::cout << "\tpkey[2]: [" << elgamal->m_y.length() << " bits]\n"; + } break; + case PublicKeyAlgorithm::Ecdsa: { + auto ecdsa = dynamic_cast(key); + const std::string oidstr = ecdsa->m_curve.as_string(); + Botan::OID oid(oidstr); + std::cout << "\tpkey[0]: [" << (1 + ecdsa->m_curve.length()) * 8 + << " bits] " << Botan::OIDS::lookup(oid) << " (" << oidstr + << ")\n"; + std::cout << "\tpkey[1]: [" << ecdsa->m_key.length() << " bits]\n"; + } break; + case PublicKeyAlgorithm::Eddsa: { + auto eddsa = dynamic_cast(key); + const std::string oidstr = eddsa->m_curve.as_string(); + Botan::OID oid(oidstr); + std::cout << "\tpkey[0]: [" << (1 + eddsa->m_curve.length()) * 8 + << " bits] " << Botan::OIDS::lookup(oid) << " (" << oidstr + << ")\n"; + std::cout << "\tpkey[1]: [" << eddsa->m_key.length() << " bits]\n"; + } break; + default: + break; + } + auto keyid = pub->keyid(); + std::cout << "\tkeyid: " << Botan::hex_encode(keyid.data(), keyid.size()) + << "\n"; + } +} + +static void output_signature_subpacket(const std::string& variant, + SignatureSubpacket* subpacket) { + std::cout << "\t" << (subpacket->m_critical ? "critical " : "") << variant + << " " << static_cast(subpacket->type()) << " len " + << subpacket->body_length(); + switch (subpacket->type()) { + case SignatureSubpacketType::SignatureCreationTime: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (sig created " << sub->m_created << ")"; + break; + } + case SignatureSubpacketType::SignatureExpirationTime: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + if (sub->m_expiration) + std::cout << " (sig expires after " << sub->m_expiration << ")"; + else + std::cout << " (sig does not expire)"; + break; + } + case SignatureSubpacketType::ExportableCertification: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + if (sub->m_exportable) + std::cout << " (exportable)"; + else + std::cout << " (not exportable)"; + break; + } + case SignatureSubpacketType::TrustSignature: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (trust signature of depth " << (int)sub->m_level + << ", value " << (int)sub->m_amount << ")"; + break; + } + case SignatureSubpacketType::RegularExpression: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + const tao::json::value str = sub->m_regex; + std::cout << " (regular expression: " << str << ")"; + break; + } + case SignatureSubpacketType::Revocable: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + if (sub->m_revocable) + std::cout << " (revocable)"; + else + std::cout << " (not revocable)"; + break; + } + case SignatureSubpacketType::KeyExpirationTime: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + if (sub->m_expiration) { + int seconds = sub->m_expiration; + int minutes = seconds / 60; + int hours = minutes / 60; + int days = hours / 24; + int years = days / 365; + std::cout << " (key expires after " << years << "y" << (days % 365) + << "d" << (hours % 24) << "h" << (minutes % 60) << "m)"; + } else + std::cout << " (key does not expire)"; + break; + } + case SignatureSubpacketType::PreferredSymmetricAlgorithms: { + auto sub = + dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (pref-sym-algos:"; + for (auto& algorithm : sub->m_algorithms) + std::cout << " " << (int)algorithm; + std::cout << ")"; + break; + } + case SignatureSubpacketType::RevocationKey: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (revocation key: c=" + << fmt::format("{:02x}", static_cast(sub->m_class)) + << " a=" << static_cast(sub->m_algorithm) << " f=" + << Botan::hex_encode(sub->m_fingerprint.data(), + sub->m_fingerprint.size()) + << ")"; + break; + } + case SignatureSubpacketType::Issuer: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (issuer key ID " + << Botan::hex_encode(sub->m_issuer.data(), sub->m_issuer.size()) + << ")"; + break; + } + case SignatureSubpacketType::NotationData: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + // FIXME: Avoid copy? + const tao::json::value name = + std::string{reinterpret_cast(sub->m_name.data()), + sub->m_name.size()}; + const tao::json::value value = + std::string{reinterpret_cast(sub->m_value.data()), + sub->m_value.size()}; + std::cout << " (notation: " << name << " = " << value << ")"; + break; + } + case SignatureSubpacketType::PreferredHashAlgorithms: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (pref-hash-algos:"; + for (auto& algorithm : sub->m_algorithms) + std::cout << " " << (int)algorithm; + std::cout << ")"; + break; + } + case SignatureSubpacketType::PreferredCompressionAlgorithms: { + auto sub = + dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (pref-zip-algos:"; + for (auto& algorithm : sub->m_algorithms) + std::cout << " " << (int)algorithm; + std::cout << ")"; + break; + } + case SignatureSubpacketType::KeyServerPreferences: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (keyserver preferences: " + << Botan::hex_encode(sub->m_flags.data(), sub->m_flags.size()) + << ")"; + break; + } + case SignatureSubpacketType::PreferredKeyServer: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + const tao::json::value str = sub->m_uri; + std::cout << " (preferred keyserver: " << str << ")"; + break; + } + case SignatureSubpacketType::PrimaryUserId: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (primary user ID: " + << fmt::format("{:02x}", static_cast(sub->m_primary)) + << ")"; + break; + } + case SignatureSubpacketType::PolicyUri: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + const tao::json::value str = sub->m_uri; + std::cout << " (policy: " << str << ")"; + break; + } + case SignatureSubpacketType::KeyFlags: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (key flags: " + << Botan::hex_encode(sub->m_flags.data(), sub->m_flags.size()) + << ")"; + break; + } + case SignatureSubpacketType::SignersUserId: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + const tao::json::value str = sub->m_user_id; + std::cout << " (signer's user ID: " << str << ")"; + break; + } + case SignatureSubpacketType::ReasonForRevocation: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + const tao::json::value str = sub->m_reason; + std::cout << " (revocation reason 0x" + << fmt::format("{:02x}", static_cast(sub->m_code)) << " " + << str << ")"; + break; + } + case SignatureSubpacketType::Features: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + std::cout << " (features: " + << Botan::hex_encode(sub->m_features.data(), + sub->m_features.size()) + << ")"; + break; + } + case SignatureSubpacketType::EmbeddedSignature: { + auto sub = dynamic_cast(subpacket); + assert(sub != nullptr); + ParserInput in(sub->m_signature.data(), sub->m_signature.size()); + auto sig = SignaturePacket::create(in); + if (sig == nullptr || !sig->m_signature) + std::cout << " (signature: invalid)"; + else { + switch (sig->m_signature->version()) { + case SignatureVersion::V2: + case SignatureVersion::V3: { + auto v3sig = dynamic_cast(sig->m_signature.get()); + std::cout << fmt::format( + " (signature: v{:d}, class 0x{:02x}, algo {:d}, digest algo " + "{:d})", + static_cast(v3sig->version()), + static_cast(v3sig->signature_type()), + static_cast(v3sig->public_key_algorithm()), + static_cast(v3sig->hash_algorithm())); + break; + } + case SignatureVersion::V4: { + auto v4sig = dynamic_cast(sig->m_signature.get()); + std::cout << fmt::format( + " (signature: v{:d}, class 0x{:02x}, algo {:d}, digest algo " + "{:d})", + static_cast(v4sig->version()), + static_cast(v4sig->signature_type()), + static_cast(v4sig->public_key_algorithm()), + static_cast(v4sig->hash_algorithm())); + break; + } + default: + std::cout << fmt::format(" (signature: v{:d})", + static_cast(sig->version())); + break; + } + } + break; + } + default: + break; + } + std::cout << "\n"; +} + +static void output_signature_data(SignatureData* sig) { + SignatureMaterial* sigmat = nullptr; + switch (sig->version()) { + case SignatureVersion::V2: + case SignatureVersion::V3: { + auto v3sig = dynamic_cast(sig); + std::cout << ":signature packet: algo " + << static_cast(v3sig->public_key_algorithm()) << "\n"; + std::cout << "\tversion 3, created " << v3sig->m_created + << ", md5len 5, sigclass 0x" + << fmt::format("{:02x}", + static_cast(v3sig->signature_type())) + << "\n"; + std::cout << "\tdigest algo " << static_cast(v3sig->hash_algorithm()) + << ", begin of digest " + << fmt::format("{:02x}", + static_cast(v3sig->m_quick.data()[0])) + << " " + << fmt::format("{:02x}", + static_cast(v3sig->m_quick.data()[1])) + << "\n"; + sigmat = v3sig->m_signature.get(); + } break; + case SignatureVersion::V4: { + auto v4sig = dynamic_cast(sig); + // FIXME: Try to get created from subpackets. + std::cout << ":signature packet: algo " + << static_cast(v4sig->public_key_algorithm()) << "\n"; + std::cout << "\tversion 4, created " << v4sig->m_created + << ", md5len 0, sigclass 0x" + << fmt::format("{:02x}", + static_cast(v4sig->signature_type())) + << "\n"; + std::cout << "\tdigest algo " << static_cast(v4sig->hash_algorithm()) + << ", begin of digest " + << fmt::format("{:02x}", + static_cast(v4sig->m_quick.data()[0])) + << " " + << fmt::format("{:02x}", + static_cast(v4sig->m_quick.data()[1])) + << "\n"; + for (auto& subpacket : v4sig->m_hashed_subpackets->m_subpackets) { + output_signature_subpacket("hashed subpkt", subpacket.get()); + } + for (auto& subpacket : v4sig->m_unhashed_subpackets->m_subpackets) { + output_signature_subpacket("subpkt", subpacket.get()); + } + sigmat = v4sig->m_signature.get(); + } break; + default: + break; + } + if (sigmat) { + switch (sigmat->algorithm()) { + case PublicKeyAlgorithm::Rsa: { + auto rsa = dynamic_cast(sigmat); + std::cout << "\tdata: [" << rsa->m_m_pow_d.length() << " bits]\n"; + } break; + case PublicKeyAlgorithm::Dsa: { + auto dsa = dynamic_cast(sigmat); + std::cout << "\tdata: [" << dsa->m_r.length() << " bits]\n"; + std::cout << "\tdata: [" << dsa->m_s.length() << " bits]\n"; + } break; + case PublicKeyAlgorithm::Ecdsa: { + auto ecdsa = dynamic_cast(sigmat); + std::cout << "\tdata: [" << ecdsa->m_r.length() << " bits]\n"; + std::cout << "\tdata: [" << ecdsa->m_s.length() << " bits]\n"; + } break; + case PublicKeyAlgorithm::Eddsa: { + auto eddsa = dynamic_cast(sigmat); + std::cout << "\tdata: [" << eddsa->m_r.length() << " bits]\n"; + std::cout << "\tdata: [" << eddsa->m_s.length() << " bits]\n"; + } break; + default: + break; + } + } +} + struct LegacyPacketSink : public RawPacketSink { void next_packet(std::unique_ptr header, const char* data, size_t length) { @@ -57,10 +490,58 @@ struct LegacyPacketSink : public RawPacketSink { auto new_header = dynamic_cast(header.get()); - std::cout << "# off=" << header->m_offset - << " ctb=" << fmt::format("{:02x}", static_cast((uint8_t)head[0])) + std::cout << rang::fg::gray << "# off=" << header->m_offset << " ctb=" + << fmt::format("{:02x}", static_cast((uint8_t)head[0])) << " tag=" << (int)header->type() << " hlen=" << head.length() - << " plen=" << length << (new_header ? " new-ctb" : "") << "\n"; + << " plen=" << length << (new_header ? " new-ctb" : "") + << rang::style::reset << "\n"; + // FIXME: Catch exception in nested parsing, show useful debug output, + // default to raw. + try { + ParserInput in{data, length}; + auto packet = Packet::create_or_throw(header->type(), in); + packet->m_header = std::move(header); + switch (packet->type()) { + case PacketType::Marker: + std::cout << ":marker packet: PGP\n"; + break; + case PacketType::UserId: { + auto uid = dynamic_cast(packet.get()); + assert(uid); + const tao::json::value str = uid->m_content; + std::cout << ":user ID packet: " << str << "\n"; + } break; + case PacketType::PublicKey: { + auto pubkey = dynamic_cast(packet.get()); + assert(pubkey); + auto pub = dynamic_cast(pubkey->m_public_key.get()); + assert(pub); + std::cout << ":public key packet:\n"; + output_public_key_data(pub); + } break; + case PacketType::PublicSubkey: { + auto pubkey = dynamic_cast(packet.get()); + assert(pubkey); + auto pub = dynamic_cast(pubkey->m_public_key.get()); + assert(pub); + std::cout << ":public sub key packet:\n"; + output_public_key_data(pub); + } break; + case PacketType::Signature: { + auto signature = dynamic_cast(packet.get()); + assert(signature); + auto sig = dynamic_cast(signature->m_signature.get()); + assert(sig); + output_signature_data(sig); + } break; + default: + break; + } + } catch (ParserError& exc) { + exc.m_pos.m_byte += header->m_offset; + std::cout << rang::style::bold << rang::fgB::red << "ERROR" + << rang::style::reset << ":" << exc.what() << "\n"; + } } void start_packet(std::unique_ptr header){}; void continue_packet(const char* data, size_t length){};