#[cfg(feature = "dnssec")]
use core::str::FromStr;
#[cfg(feature = "dnssec")]
use core::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "dnssec")]
use dnssec_prover::rr::RR;
#[cfg(feature = "dnssec")]
use dnssec_prover::ser::parse_rr_stream;
#[cfg(feature = "dnssec")]
use dnssec_prover::validation::verify_rr_stream;
use dnssec_prover::rr::Name;
use lightning_types::features::NodeFeatures;
use core::fmt;
use crate::blinded_path::message::DNSResolverContext;
use crate::io;
#[cfg(feature = "dnssec")]
use crate::ln::channelmanager::PaymentId;
use crate::ln::msgs::DecodeError;
#[cfg(feature = "dnssec")]
use crate::offers::offer::Offer;
use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction};
use crate::onion_message::packet::OnionMessageContents;
use crate::prelude::*;
#[cfg(feature = "dnssec")]
use crate::sign::EntropySource;
#[cfg(feature = "dnssec")]
use crate::sync::Mutex;
use crate::util::ser::{Hostname, Readable, ReadableArgs, Writeable, Writer};
pub trait DNSResolverMessageHandler {
fn handle_dnssec_query(
&self, message: DNSSECQuery, responder: Option<Responder>,
) -> Option<(DNSResolverMessage, ResponseInstruction)>;
fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext);
fn provided_node_features(&self) -> NodeFeatures {
NodeFeatures::empty()
}
fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
vec![]
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum DNSResolverMessage {
DNSSECQuery(DNSSECQuery),
DNSSECProof(DNSSECProof),
}
const DNSSEC_QUERY_TYPE: u64 = 65536;
const DNSSEC_PROOF_TYPE: u64 = 65538;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct DNSSECQuery(pub Name);
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct DNSSECProof {
pub name: Name,
pub proof: Vec<u8>,
}
impl DNSResolverMessage {
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,
}
}
}
const REQUIRED_EXTRA_LEN: usize = ".user._bitcoin-payment.".len() + 1;
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct HumanReadableName {
contents: [u8; 255 - REQUIRED_EXTRA_LEN],
user_len: u8,
domain_len: u8,
}
impl HumanReadableName {
pub fn new(user: &str, mut domain: &str) -> Result<HumanReadableName, ()> {
if domain.ends_with('.') {
domain = &domain[..domain.len() - 1];
}
if user.len() + domain.len() + REQUIRED_EXTRA_LEN > 255 {
return Err(());
}
if user.is_empty() || domain.is_empty() {
return Err(());
}
if !Hostname::str_is_valid_hostname(&user) || !Hostname::str_is_valid_hostname(&domain) {
return Err(());
}
let mut contents = [0; 255 - REQUIRED_EXTRA_LEN];
contents[..user.len()].copy_from_slice(user.as_bytes());
contents[user.len()..user.len() + domain.len()].copy_from_slice(domain.as_bytes());
Ok(HumanReadableName {
contents,
user_len: user.len() as u8,
domain_len: domain.len() as u8,
})
}
pub fn from_encoded(encoded: &str) -> Result<HumanReadableName, ()> {
if let Some((user, domain)) = encoded.strip_prefix('₿').unwrap_or(encoded).split_once("@")
{
Self::new(user, domain)
} else {
Err(())
}
}
pub fn user(&self) -> &str {
let bytes = &self.contents[..self.user_len as usize];
core::str::from_utf8(bytes).expect("Checked in constructor")
}
pub fn domain(&self) -> &str {
let user_len = self.user_len as usize;
let bytes = &self.contents[user_len..user_len + self.domain_len as usize];
core::str::from_utf8(bytes).expect("Checked in constructor")
}
}
impl Writeable for HumanReadableName {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
(self.user().len() as u8).write(writer)?;
writer.write_all(&self.user().as_bytes())?;
(self.domain().len() as u8).write(writer)?;
writer.write_all(&self.domain().as_bytes())
}
}
impl Readable for HumanReadableName {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let mut user_bytes = [0; 255];
let user_len: u8 = Readable::read(reader)?;
reader.read_exact(&mut user_bytes[..user_len as usize])?;
let user = match core::str::from_utf8(&user_bytes[..user_len as usize]) {
Ok(user) => user,
Err(_) => return Err(DecodeError::InvalidValue),
};
let mut domain_bytes = [0; 255];
let domain_len: u8 = Readable::read(reader)?;
reader.read_exact(&mut domain_bytes[..domain_len as usize])?;
let domain = match core::str::from_utf8(&domain_bytes[..domain_len as usize]) {
Ok(domain) => domain,
Err(_) => return Err(DecodeError::InvalidValue),
};
HumanReadableName::new(user, domain).map_err(|()| DecodeError::InvalidValue)
}
}
impl fmt::Display for HumanReadableName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "₿{}@{}", self.user(), self.domain())
}
}
#[cfg(feature = "dnssec")]
struct PendingResolution {
start_height: u32,
context: DNSResolverContext,
name: HumanReadableName,
payment_id: PaymentId,
}
#[cfg(feature = "dnssec")]
pub struct OMNameResolver {
pending_resolves: Mutex<HashMap<Name, Vec<PendingResolution>>>,
latest_block_time: AtomicUsize,
latest_block_height: AtomicUsize,
}
#[cfg(feature = "dnssec")]
impl OMNameResolver {
pub fn new(latest_block_time: u32, latest_block_height: u32) -> Self {
Self {
pending_resolves: Mutex::new(new_hash_map()),
latest_block_time: AtomicUsize::new(latest_block_time as usize),
latest_block_height: AtomicUsize::new(latest_block_height as usize),
}
}
pub fn new_without_no_std_expiry_validation() -> Self {
Self {
pending_resolves: Mutex::new(new_hash_map()),
latest_block_time: AtomicUsize::new(0),
latest_block_height: AtomicUsize::new(0),
}
}
pub fn new_best_block(&self, height: u32, time: u32) {
self.latest_block_time.store(time as usize, Ordering::Release);
self.latest_block_height.store(height as usize, Ordering::Release);
let mut resolves = self.pending_resolves.lock().unwrap();
resolves.retain(|_, queries| {
queries.retain(|query| query.start_height >= height - 1);
!queries.is_empty()
});
}
pub fn expire_pending_resolution(&self, name: &HumanReadableName, payment_id: PaymentId) {
let dns_name =
Name::try_from(format!("{}.user._bitcoin-payment.{}.", name.user(), name.domain()));
debug_assert!(
dns_name.is_ok(),
"The HumanReadableName constructor shouldn't allow names which are too long"
);
if let Ok(name) = dns_name {
let mut pending_resolves = self.pending_resolves.lock().unwrap();
if let hash_map::Entry::Occupied(mut entry) = pending_resolves.entry(name) {
let resolutions = entry.get_mut();
resolutions.retain(|resolution| resolution.payment_id != payment_id);
if resolutions.is_empty() {
entry.remove();
}
}
}
}
pub fn resolve_name<ES: EntropySource + ?Sized>(
&self, payment_id: PaymentId, name: HumanReadableName, entropy_source: &ES,
) -> Result<(DNSSECQuery, DNSResolverContext), ()> {
let dns_name =
Name::try_from(format!("{}.user._bitcoin-payment.{}.", name.user(), name.domain()));
debug_assert!(
dns_name.is_ok(),
"The HumanReadableName constructor shouldn't allow names which are too long"
);
let mut context = DNSResolverContext { nonce: [0; 16] };
context.nonce.copy_from_slice(&entropy_source.get_secure_random_bytes()[..16]);
if let Ok(dns_name) = dns_name {
let start_height = self.latest_block_height.load(Ordering::Acquire) as u32;
let mut pending_resolves = self.pending_resolves.lock().unwrap();
let context_ret = context.clone();
let resolution = PendingResolution { start_height, context, name, payment_id };
pending_resolves.entry(dns_name.clone()).or_insert_with(Vec::new).push(resolution);
Ok((DNSSECQuery(dns_name), context_ret))
} else {
Err(())
}
}
pub fn handle_dnssec_proof_for_offer(
&self, msg: DNSSECProof, context: DNSResolverContext,
) -> Option<(Vec<(HumanReadableName, PaymentId)>, Offer)> {
let (completed_requests, uri) = self.handle_dnssec_proof_for_uri(msg, context)?;
if let Some((_onchain, params)) = uri.split_once("?") {
for param in params.split("&") {
let (k, v) = if let Some(split) = param.split_once("=") {
split
} else {
continue;
};
if k.eq_ignore_ascii_case("lno") {
if let Ok(offer) = Offer::from_str(v) {
return Some((completed_requests, offer));
}
return None;
}
}
}
None
}
pub fn handle_dnssec_proof_for_uri(
&self, msg: DNSSECProof, context: DNSResolverContext,
) -> Option<(Vec<(HumanReadableName, PaymentId)>, String)> {
let DNSSECProof { name: answer_name, proof } = msg;
let mut pending_resolves = self.pending_resolves.lock().unwrap();
if let hash_map::Entry::Occupied(entry) = pending_resolves.entry(answer_name) {
if !entry.get().iter().any(|query| query.context == context) {
return None;
}
let parsed_rrs = parse_rr_stream(&proof);
let validated_rrs =
parsed_rrs.as_ref().and_then(|rrs| verify_rr_stream(rrs).map_err(|_| &()));
if let Ok(validated_rrs) = validated_rrs {
#[allow(unused_assignments, unused_mut)]
let mut time = self.latest_block_time.load(Ordering::Acquire) as u64;
#[cfg(feature = "std")]
{
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH);
time = now.expect("Time must be > 1970").as_secs();
}
if time != 0 {
let max_time_offset = if cfg!(feature = "std") { 0 } else { 60 * 2 };
if validated_rrs.valid_from > time + max_time_offset {
return None;
}
if validated_rrs.expires < time - max_time_offset {
return None;
}
}
let resolved_rrs = validated_rrs.resolve_name(&entry.key());
if resolved_rrs.is_empty() {
return None;
}
let (_, requests) = entry.remove_entry();
const URI_PREFIX: &str = "bitcoin:";
let mut candidate_records = resolved_rrs
.iter()
.filter_map(
|rr| if let RR::Txt(txt) = rr { Some(txt.data.as_vec()) } else { None },
)
.filter_map(|data| String::from_utf8(data).ok())
.filter(|data_string| data_string.len() > URI_PREFIX.len())
.filter(|data_string| {
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
});
match (candidate_records.next(), candidate_records.next()) {
(Some(txt), None) => {
let completed_requests =
requests.into_iter().map(|r| (r.name, r.payment_id)).collect();
return Some((completed_requests, txt));
},
_ => {},
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hrn_display_format() {
let user = "user";
let domain = "example.com";
let hrn = HumanReadableName::new(user, domain)
.expect("Failed to create HumanReadableName for user");
let expected_display = format!("₿{}@{}", user, domain);
assert_eq!(
format!("{}", hrn),
expected_display,
"HumanReadableName display format mismatch"
);
}
#[test]
#[cfg(feature = "dnssec")]
fn test_expiry() {
let keys = crate::sign::KeysManager::new(&[33; 32], 0, 0, true);
let resolver = OMNameResolver::new(42, 42);
let name = HumanReadableName::new("user", "example.com").unwrap();
resolver.resolve_name(PaymentId([0; 32]), name.clone(), &keys).unwrap();
assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
resolver.new_best_block(44, 42);
assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 0);
resolver.resolve_name(PaymentId([1; 32]), name.clone(), &keys).unwrap();
assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
resolver.new_best_block(45, 42);
assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
assert_eq!(resolver.pending_resolves.lock().unwrap().iter().next().unwrap().1.len(), 1);
resolver.resolve_name(PaymentId([2; 32]), name.clone(), &keys).unwrap();
resolver.resolve_name(PaymentId([3; 32]), name.clone(), &keys).unwrap();
assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
assert_eq!(resolver.pending_resolves.lock().unwrap().iter().next().unwrap().1.len(), 3);
resolver.new_best_block(46, 42);
assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
assert_eq!(resolver.pending_resolves.lock().unwrap().iter().next().unwrap().1.len(), 2);
resolver.expire_pending_resolution(&name, PaymentId([3; 32]));
assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
assert_eq!(resolver.pending_resolves.lock().unwrap().iter().next().unwrap().1.len(), 1);
resolver.new_best_block(47, 42);
assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 0);
}
}