]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Add DNS(SEC) query and proof messages and onion message handler
authorMatt Corallo <git@bluematt.me>
Mon, 30 Sep 2024 16:16:36 +0000 (16:16 +0000)
committerMatt Corallo <git@bluematt.me>
Mon, 30 Sep 2024 16:19:31 +0000 (16:19 +0000)
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.

lightning/Cargo.toml
lightning/src/blinded_path/message.rs
lightning/src/onion_message/dns_resolution.rs [new file with mode: 0644]
lightning/src/onion_message/mod.rs
lightning/src/util/ser.rs

index 3a1939733a6bca92b6f9dd2a6d59bcf349516741..e0eb504314686056532516103935358fffe8406d 100644 (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 }
 
index 5b6b5fa6dbdd3df0a2fabd176d3638d83a8556ca..2cad33887f41feb9ce699b297378a34db038f56b 100644 (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.
diff --git a/lightning/src/onion_message/dns_resolution.rs b/lightning/src/onion_message/dns_resolution.rs
new file mode 100644 (file)
index 0000000..3d53126
--- /dev/null
@@ -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,
+               }
+       }
+}
index 8cdf098a3e5844f7dc56cb6cfb60f5126cc73f47..a5735e372f34cff73eb9b551919e37fed7cb2f84 100644 (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;
index 99d20b927b6734764fcfec1100e2d299f6bca391..b847f9eebd73827e3720f0db86a8d980ba6f5ee8 100644 (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]