mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-10 18:19:03 +00:00
LibDNS: Add support for local DNSSEC validation
This commit is contained in:
parent
59b6293182
commit
b24fb0a836
Notes:
github-actions[bot]
2025-06-11 16:17:52 +00:00
Author: https://github.com/alimpfard
Commit: b24fb0a836
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4709
Reviewed-by: https://github.com/ADKaster ✅
5 changed files with 880 additions and 69 deletions
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/ByteReader.h>
|
||||
#include <AK/CountingStream.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Stream.h>
|
||||
|
@ -662,11 +663,17 @@ ErrorOr<DomainName> DomainName::from_raw(ParseContext& ctx)
|
|||
constexpr static u8 OffsetMarkerMask = 0b11000000;
|
||||
if ((length & OffsetMarkerMask) == OffsetMarkerMask) {
|
||||
// This is a pointer to a prior domain name.
|
||||
u16 const offset = static_cast<u16>(length & ~OffsetMarkerMask) << 8 | TRY(ctx.stream.read_value<u8>());
|
||||
u16 offset = static_cast<u16>(length & ~OffsetMarkerMask) << 8 | TRY(ctx.stream.read_value<u8>());
|
||||
if (auto it = ctx.pointers->find_largest_not_above_iterator(offset); !it.is_end()) {
|
||||
auto labels = it->labels;
|
||||
for (auto& entry : labels)
|
||||
name.labels.append(entry);
|
||||
size_t start_index = 0;
|
||||
size_t start_entry_offset = offset - it.key();
|
||||
while (start_entry_offset > 0 && start_index < labels.size()) {
|
||||
start_entry_offset -= labels[start_index].length() + 1; // +1 for the length byte
|
||||
start_index++;
|
||||
}
|
||||
for (size_t i = start_index; i < labels.size(); ++i)
|
||||
name.labels.append(labels[i].substring_view(i == start_index ? start_entry_offset : 0));
|
||||
break;
|
||||
}
|
||||
dbgln("Invalid domain name pointer in label, no prior domain name found around offset {}", offset);
|
||||
|
@ -702,6 +709,9 @@ ErrorOr<void> DomainName::to_raw(ByteBuffer& out) const
|
|||
|
||||
String DomainName::to_string() const
|
||||
{
|
||||
if (labels.is_empty())
|
||||
return "."_string;
|
||||
|
||||
StringBuilder builder;
|
||||
for (size_t i = 0; i < labels.size(); ++i) {
|
||||
builder.append(labels[i]);
|
||||
|
@ -711,6 +721,26 @@ String DomainName::to_string() const
|
|||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
String DomainName::to_canonical_string() const
|
||||
{
|
||||
if (labels.is_empty())
|
||||
return "."_string;
|
||||
|
||||
StringBuilder builder;
|
||||
for (size_t i = 0; i < labels.size(); ++i) {
|
||||
auto& label = labels[i];
|
||||
for (size_t j = 0; j < label.length(); ++j) {
|
||||
auto ch = label[j];
|
||||
if (ch >= 'A' && ch <= 'Z')
|
||||
ch = to_ascii_lowercase(ch);
|
||||
builder.append(ch);
|
||||
}
|
||||
builder.append('.');
|
||||
}
|
||||
|
||||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
class RecordingStream final : public Stream {
|
||||
public:
|
||||
explicit RecordingStream(Stream& stream)
|
||||
|
@ -762,9 +792,10 @@ ErrorOr<ResourceRecord> ResourceRecord::from_raw(ParseContext& ctx)
|
|||
ResourceType type;
|
||||
Class class_;
|
||||
u32 ttl;
|
||||
size_t original_offset = ctx.stream.read_bytes();
|
||||
{
|
||||
RecordingStream rr_stream { ctx.stream };
|
||||
CountingStream rr_counting_stream { MaybeOwned<Stream>(rr_stream) };
|
||||
CountingStream rr_counting_stream { MaybeOwned<Stream>(rr_stream), original_offset };
|
||||
ParseContext rr_ctx { rr_counting_stream, move(ctx.pointers) };
|
||||
ScopeGuard guard([&] { ctx.pointers = move(rr_ctx.pointers); });
|
||||
|
||||
|
@ -785,13 +816,14 @@ ErrorOr<ResourceRecord> ResourceRecord::from_raw(ParseContext& ctx)
|
|||
class_ = static_cast<Class>(static_cast<u16>(TRY(rr_ctx.stream.read_value<NetworkOrdered<u16>>())));
|
||||
ttl = static_cast<u32>(TRY(rr_ctx.stream.read_value<NetworkOrdered<u32>>()));
|
||||
auto rd_length = static_cast<u16>(TRY(rr_ctx.stream.read_value<NetworkOrdered<u16>>()));
|
||||
original_offset = rr_ctx.stream.read_bytes();
|
||||
TRY(rr_ctx.stream.read_until_filled(TRY(rdata.get_bytes_for_writing(rd_length))));
|
||||
|
||||
rr_raw_data = move(rr_stream).take_recorded_data();
|
||||
}
|
||||
|
||||
FixedMemoryStream stream { rdata.bytes() };
|
||||
CountingStream rdata_stream { MaybeOwned<Stream>(stream) };
|
||||
CountingStream rdata_stream { MaybeOwned<Stream>(stream), original_offset };
|
||||
ParseContext rdata_ctx { rdata_stream, move(ctx.pointers) };
|
||||
ScopeGuard guard([&] { ctx.pointers = move(rdata_ctx.pointers); });
|
||||
|
||||
|
@ -887,9 +919,11 @@ ErrorOr<void> ResourceRecord::to_raw(ByteBuffer& buffer) const
|
|||
ErrorOr<String> ResourceRecord::to_string() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.appendff("[{} {} ", Messages::to_string(class_), Messages::to_string(type));
|
||||
record.visit(
|
||||
[&](auto const& record) { builder.appendff("{}", MUST(record.to_string())); },
|
||||
[&](ByteBuffer const& raw) { builder.appendff("{:hex-dump}", raw.bytes()); });
|
||||
builder.appendff(" | ttl={}, name={}]", ttl, name.to_string());
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
|
@ -902,6 +936,15 @@ ErrorOr<Records::A> Records::A::from_raw(ParseContext& ctx)
|
|||
return Records::A { IPv4Address { address } };
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::A::to_raw(ByteBuffer& buffer) const
|
||||
{
|
||||
auto const address = this->address.to_u32();
|
||||
auto const net_address = bit_cast<NetworkOrdered<u32>>(address);
|
||||
auto bytes = TRY(buffer.get_bytes_for_writing(sizeof(net_address)));
|
||||
bytes.overwrite(0, &net_address, sizeof(net_address));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Records::AAAA> Records::AAAA::from_raw(ParseContext& ctx)
|
||||
{
|
||||
// RFC 3596, 2.2. AAAA RDATA format.
|
||||
|
@ -911,6 +954,18 @@ ErrorOr<Records::AAAA> Records::AAAA::from_raw(ParseContext& ctx)
|
|||
return Records::AAAA { IPv6Address { bit_cast<Array<u8, 16>>(address) } };
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::AAAA::to_raw(ByteBuffer& buffer) const
|
||||
{
|
||||
auto const* const address_bytes = this->address.to_in6_addr_t();
|
||||
u128 address {};
|
||||
memcpy(&address, address_bytes, sizeof(address));
|
||||
|
||||
auto const net_address = bit_cast<NetworkOrdered<u128>>(address);
|
||||
auto bytes = TRY(buffer.get_bytes_for_writing(sizeof(net_address)));
|
||||
bytes.overwrite(0, &net_address, sizeof(net_address));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Records::TXT> Records::TXT::from_raw(ParseContext& ctx)
|
||||
{
|
||||
// RFC 1035, 3.3.14. TXT RDATA format.
|
||||
|
@ -922,6 +977,18 @@ ErrorOr<Records::TXT> Records::TXT::from_raw(ParseContext& ctx)
|
|||
return Records::TXT { ByteString::copy(content) };
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::TXT::to_raw(ByteBuffer& buffer) const
|
||||
{
|
||||
auto const length = static_cast<u8>(content.length());
|
||||
auto length_bytes = TRY(buffer.get_bytes_for_writing(1));
|
||||
memcpy(length_bytes.data(), &length, 1);
|
||||
|
||||
auto content_bytes = TRY(buffer.get_bytes_for_writing(length));
|
||||
memcpy(content_bytes.data(), content.characters(), length);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Records::CNAME> Records::CNAME::from_raw(ParseContext& ctx)
|
||||
{
|
||||
// RFC 1035, 3.3.1. CNAME RDATA format.
|
||||
|
@ -931,6 +998,11 @@ ErrorOr<Records::CNAME> Records::CNAME::from_raw(ParseContext& ctx)
|
|||
return Records::CNAME { move(name) };
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::CNAME::to_raw(ByteBuffer& buffer) const
|
||||
{
|
||||
return names.to_raw(buffer);
|
||||
}
|
||||
|
||||
ErrorOr<Records::NS> Records::NS::from_raw(ParseContext& ctx)
|
||||
{
|
||||
// RFC 1035, 3.3.11. NS RDATA format.
|
||||
|
@ -962,6 +1034,23 @@ ErrorOr<Records::SOA> Records::SOA::from_raw(ParseContext& ctx)
|
|||
return Records::SOA { move(mname), move(rname), serial, refresh, retry, expire, minimum };
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::SOA::to_raw(ByteBuffer& buffer) const
|
||||
{
|
||||
TRY(mname.to_raw(buffer));
|
||||
TRY(rname.to_raw(buffer));
|
||||
|
||||
auto const output_size = 5 * sizeof(u32);
|
||||
FixedMemoryStream stream { TRY(buffer.get_bytes_for_writing(output_size)) };
|
||||
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u32>>(serial)));
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u32>>(refresh)));
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u32>>(retry)));
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u32>>(expire)));
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u32>>(minimum)));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Records::MX> Records::MX::from_raw(ParseContext& ctx)
|
||||
{
|
||||
// RFC 1035, 3.3.9. MX RDATA format.
|
||||
|
@ -1005,11 +1094,36 @@ ErrorOr<Records::DNSKEY> Records::DNSKEY::from_raw(ParseContext& ctx)
|
|||
// | ALGORITHM| an 8-bit value that identifies the public key's cryptographic algorithm.
|
||||
// | PUBLICKEY| the public key material.
|
||||
|
||||
u32 key_tag = 0;
|
||||
auto flags = static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
|
||||
key_tag += (bit_cast<u16>(NetworkOrdered<u16>(flags)) & 0xff) << 8;
|
||||
key_tag += (bit_cast<u16>(NetworkOrdered<u16>(flags)) >> 8) & 0xff;
|
||||
auto protocol = TRY(ctx.stream.read_value<u8>());
|
||||
key_tag += static_cast<u16>(protocol) << 8;
|
||||
auto algorithm = static_cast<DNSSEC::Algorithm>(static_cast<u8>(TRY(ctx.stream.read_value<u8>())));
|
||||
key_tag += static_cast<u16>(algorithm);
|
||||
auto public_key = TRY(ctx.stream.read_until_eof());
|
||||
return Records::DNSKEY { flags, protocol, algorithm, move(public_key) };
|
||||
for (size_t i = 0; i < public_key.size(); ++i) {
|
||||
key_tag += (i & 1) ? static_cast<u16>(public_key[i]) : static_cast<u16>(public_key[i]) << 8;
|
||||
}
|
||||
key_tag += (key_tag >> 16) & 0xffff;
|
||||
|
||||
if (public_key.is_empty())
|
||||
return Error::from_string_literal("Empty public key in DNSKEY record");
|
||||
|
||||
return Records::DNSKEY { flags, protocol, algorithm, move(public_key), static_cast<u16>(key_tag & 0xffff) };
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::DNSKEY::to_raw(ByteBuffer& buffer) const
|
||||
{
|
||||
auto const output_size = 2 + 1 + 1 + public_key.size();
|
||||
FixedMemoryStream stream { TRY(buffer.get_bytes_for_writing(output_size)) };
|
||||
|
||||
TRY(stream.write_value(static_cast<u16>(bit_cast<NetworkOrdered<u16>>(flags))));
|
||||
TRY(stream.write_value(protocol));
|
||||
TRY(stream.write_value(to_underlying(algorithm)));
|
||||
TRY(stream.write_until_depleted(public_key.bytes()));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Records::DS> Records::DS::from_raw(ParseContext& ctx)
|
||||
|
@ -1051,6 +1165,19 @@ ErrorOr<Records::DS> Records::DS::from_raw(ParseContext& ctx)
|
|||
return Records::DS { key_tag, algorithm, digest_type, move(digest) };
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::DS::to_raw(ByteBuffer& buffer) const
|
||||
{
|
||||
auto const output_size = 2 + 1 + 1 + digest.size();
|
||||
FixedMemoryStream stream { TRY(buffer.get_bytes_for_writing(output_size)) };
|
||||
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u16>>(key_tag)));
|
||||
TRY(stream.write_value(static_cast<u8>(algorithm)));
|
||||
TRY(stream.write_value(static_cast<u8>(digest_type)));
|
||||
TRY(stream.write_until_depleted(digest.bytes()));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Records::SIG> Records::SIG::from_raw(ParseContext& ctx)
|
||||
{
|
||||
// RFC 4034, 2.2. The SIG Resource Record.
|
||||
|
@ -1077,6 +1204,29 @@ ErrorOr<Records::SIG> Records::SIG::from_raw(ParseContext& ctx)
|
|||
return Records::SIG { type_covered, algorithm, labels, original_ttl, UnixDateTime::from_seconds_since_epoch(signature_expiration), UnixDateTime::from_seconds_since_epoch(signature_inception), key_tag, move(signer_name), move(signature) };
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::SIG::to_raw_excluding_signature(ByteBuffer& buffer) const
|
||||
{
|
||||
AllocatingMemoryStream stream;
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u16>>(to_underlying(type_covered))));
|
||||
TRY(stream.write_value(static_cast<u8>(algorithm)));
|
||||
TRY(stream.write_value(label_count));
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u32>>(original_ttl)));
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u32>>(expiration.seconds_since_epoch())));
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u32>>(inception.seconds_since_epoch())));
|
||||
TRY(stream.write_value(static_cast<NetworkOrdered<u16>>(key_tag)));
|
||||
|
||||
TRY(stream.read_until_filled(TRY(buffer.get_bytes_for_writing(stream.used_buffer_size()))));
|
||||
TRY(signers_name.to_raw(buffer));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::SIG::to_raw(ByteBuffer& buffer) const
|
||||
{
|
||||
TRY(to_raw_excluding_signature(buffer));
|
||||
TRY(buffer.try_append(signature));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<String> Records::SIG::to_string() const
|
||||
{
|
||||
// Single line:
|
||||
|
@ -1110,6 +1260,18 @@ ErrorOr<Records::HINFO> Records::HINFO::from_raw(ParseContext& ctx)
|
|||
return Records::HINFO { ByteString::copy(cpu), ByteString::copy(os) };
|
||||
}
|
||||
|
||||
ErrorOr<void> Records::HINFO::to_raw(ByteBuffer& buffer) const
|
||||
{
|
||||
auto allocated_length = cpu.length() + os.length() + 2;
|
||||
auto bytes = TRY(buffer.get_bytes_for_writing(allocated_length));
|
||||
FixedMemoryStream stream { bytes };
|
||||
TRY(stream.write_value(static_cast<u8>(cpu.length())));
|
||||
TRY(stream.write_until_depleted(cpu.bytes()));
|
||||
TRY(stream.write_value(static_cast<u8>(os.length())));
|
||||
TRY(stream.write_until_depleted(os.bytes()));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Records::OPT> Records::OPT::from_raw(ParseContext& ctx)
|
||||
{
|
||||
// RFC 6891, 6.1. The OPT pseudo-RR.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue