Add DNS(SEC) query and proof messages and onion message handler

This creates the initial DNSSEC proof and query messages in a new
module in `onion_message`, as well as a new message handler to
handle them.

In the coming commits, a default implementation will be added which
verifies DNSSEC proofs which can be used to resolve BIP 353 URIs
without relying on anything outside of the lightning network.
This commit is contained in:
Matt Corallo 2024-09-30 16:16:36 +00:00
parent 151a8a1aaf
commit 1cf0393056
5 changed files with 165 additions and 2 deletions

View file

@ -43,8 +43,10 @@ lightning-invoice = { version = "0.32.0", path = "../lightning-invoice", default
bech32 = { version = "0.9.1", default-features = false }
bitcoin = { version = "0.32.2", default-features = false, features = ["secp-recovery"] }
dnssec-prover = { version = "0.6", default-features = false }
hashbrown = { version = "0.13", default-features = false }
possiblyrandom = { version = "0.2", path = "../possiblyrandom", default-features = false }
regex = { version = "1.5.6", optional = true }
backtrace = { version = "0.3", optional = true }

View file

@ -285,7 +285,9 @@ pub enum MessageContext {
/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
AsyncPayments(AsyncPaymentsContext),
/// Represents a context for a blinded path used in a reply path when requesting a DNSSEC proof
/// in a `DNSResolverMessage`.
/// in a [`DNSResolverMessage`].
///
/// [`DNSResolverMessage`]: crate::onion_message::dns_resolution::DNSResolverMessage
DNSResolver(DNSResolverContext),
/// Context specific to a [`CustomOnionMessageHandler::CustomMessage`].
///
@ -434,7 +436,9 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
/// Contains a simple nonce for use in a blinded path's context.
///
/// Such a context is required when receiving a `DNSSECProof` message.
/// Such a context is required when receiving a [`DNSSECProof`] message.
///
/// [`DNSSECProof`]: crate::onion_message::dns_resolution::DNSSECProof
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct DNSResolverContext {
/// A nonce which uniquely describes a DNS resolution.

View file

@ -0,0 +1,146 @@
// This file is Copyright its original authors, visible in version control
// history.
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
//! This module defines message handling for DNSSEC proof fetching using [bLIP 32].
//!
//! It contains [`DNSResolverMessage`]s as well as a [`DNSResolverMessageHandler`] trait to handle
//! such messages using an [`OnionMessenger`].
//!
//! [bLIP 32]: https://github.com/lightning/blips/blob/master/blip-0032.md
//! [`OnionMessenger`]: super::messenger::OnionMessenger
use dnssec_prover::rr::Name;
use crate::blinded_path::message::DNSResolverContext;
use crate::io;
use crate::ln::msgs::DecodeError;
use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction};
use crate::onion_message::packet::OnionMessageContents;
use crate::prelude::*;
use crate::util::ser::{Hostname, Readable, ReadableArgs, Writeable, Writer};
/// A handler for an [`OnionMessage`] containing a DNS(SEC) query or a DNSSEC proof
///
/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
pub trait DNSResolverMessageHandler {
/// Handle a [`DNSSECQuery`] message.
///
/// If we provide DNS resolution services to third parties, we should respond with a
/// [`DNSSECProof`] message.
fn handle_dnssec_query(
&self, message: DNSSECQuery, responder: Option<Responder>,
) -> Option<(DNSResolverMessage, ResponseInstruction)>;
/// Handle a [`DNSSECProof`] message (in response to a [`DNSSECQuery`] we presumably sent).
///
/// With this, we should be able to validate the DNS record we requested.
fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext);
/// Release any [`DNSResolverMessage`]s that need to be sent.
fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
vec![]
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
/// An enum containing the possible onion messages which are used uses to request and receive
/// DNSSEC proofs.
pub enum DNSResolverMessage {
/// A query requesting a DNSSEC proof
DNSSECQuery(DNSSECQuery),
/// A response containing a DNSSEC proof
DNSSECProof(DNSSECProof),
}
const DNSSEC_QUERY_TYPE: u64 = 65536;
const DNSSEC_PROOF_TYPE: u64 = 65538;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
/// A message which is sent to a DNSSEC prover requesting a DNSSEC proof for the given name.
pub struct DNSSECQuery(pub Name);
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
/// A message which is sent in response to [`DNSSECQuery`] containing a DNSSEC proof.
pub struct DNSSECProof {
/// The name which the query was for. The proof may not contain a DNS RR for exactly this name
/// if it contains a wildcard RR which contains this name instead.
pub name: Name,
/// An [RFC 9102 DNSSEC AuthenticationChain] providing a DNSSEC proof.
///
/// [RFC 9102 DNSSEC AuthenticationChain]: https://www.rfc-editor.org/rfc/rfc9102.html#name-dnssec-authentication-chain
pub proof: Vec<u8>,
}
impl DNSResolverMessage {
/// Returns whether `tlv_type` corresponds to a TLV record for DNS Resolvers.
pub fn is_known_type(tlv_type: u64) -> bool {
match tlv_type {
DNSSEC_QUERY_TYPE | DNSSEC_PROOF_TYPE => true,
_ => false,
}
}
}
impl Writeable for DNSResolverMessage {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
match self {
Self::DNSSECQuery(DNSSECQuery(q)) => {
(q.as_str().len() as u8).write(w)?;
w.write_all(&q.as_str().as_bytes())
},
Self::DNSSECProof(DNSSECProof { name, proof }) => {
(name.as_str().len() as u8).write(w)?;
w.write_all(&name.as_str().as_bytes())?;
proof.write(w)
},
}
}
}
impl ReadableArgs<u64> for DNSResolverMessage {
fn read<R: io::Read>(r: &mut R, message_type: u64) -> Result<Self, DecodeError> {
match message_type {
DNSSEC_QUERY_TYPE => {
let s = Hostname::read(r)?;
let name = s.try_into().map_err(|_| DecodeError::InvalidValue)?;
Ok(DNSResolverMessage::DNSSECQuery(DNSSECQuery(name)))
},
DNSSEC_PROOF_TYPE => {
let s = Hostname::read(r)?;
let name = s.try_into().map_err(|_| DecodeError::InvalidValue)?;
let proof = Readable::read(r)?;
Ok(DNSResolverMessage::DNSSECProof(DNSSECProof { name, proof }))
},
_ => Err(DecodeError::InvalidValue),
}
}
}
impl OnionMessageContents for DNSResolverMessage {
#[cfg(c_bindings)]
fn msg_type(&self) -> String {
match self {
DNSResolverMessage::DNSSECQuery(_) => "DNS(SEC) Query".to_string(),
DNSResolverMessage::DNSSECProof(_) => "DNSSEC Proof".to_string(),
}
}
#[cfg(not(c_bindings))]
fn msg_type(&self) -> &'static str {
match self {
DNSResolverMessage::DNSSECQuery(_) => "DNS(SEC) Query",
DNSResolverMessage::DNSSECProof(_) => "DNSSEC Proof",
}
}
fn tlv_type(&self) -> u64 {
match self {
DNSResolverMessage::DNSSECQuery(_) => DNSSEC_QUERY_TYPE,
DNSResolverMessage::DNSSECProof(_) => DNSSEC_PROOF_TYPE,
}
}
}

View file

@ -22,6 +22,7 @@
//! [`OnionMessenger`]: self::messenger::OnionMessenger
pub mod async_payments;
pub mod dns_resolution;
pub mod messenger;
pub mod offers;
pub mod packet;

View file

@ -37,6 +37,9 @@ use bitcoin::hashes::hmac::Hmac;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hash_types::{Txid, BlockHash};
use dnssec_prover::rr::Name;
use core::time::Duration;
use crate::chain::ClaimId;
use crate::ln::msgs::DecodeError;
@ -1551,6 +1554,13 @@ impl Readable for Hostname {
}
}
impl TryInto<Name> for Hostname {
type Error = ();
fn try_into(self) -> Result<Name, ()> {
Name::try_from(self.0)
}
}
/// This is not exported to bindings users as `Duration`s are simply mapped as ints.
impl Writeable for Duration {
#[inline]