From 3900c625b3624b300d64951a0597af9377331d4e Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sun, 14 Apr 2024 17:21:52 +0000 Subject: [PATCH] Expose uniffi bindings for building and verifying proofs --- uniffi/Cargo.toml | 23 ++++++++++ uniffi/build.rs | 3 ++ uniffi/src/interface.udl | 10 +++++ uniffi/src/lib.rs | 97 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 uniffi/Cargo.toml create mode 100644 uniffi/build.rs create mode 100644 uniffi/src/interface.udl create mode 100644 uniffi/src/lib.rs diff --git a/uniffi/Cargo.toml b/uniffi/Cargo.toml new file mode 100644 index 0000000..8a223a8 --- /dev/null +++ b/uniffi/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "dnssec-prover-uniffi" +version = "0.1.0" +authors = ["Matt Corallo"] +license = "MIT OR Apache-2.0" +repository = "https://git.bitcoin.ninja/index.cgi?p=dnssec-prover" +description = "A simple crate which allows for the creation and validation of transferrable proofs of entries in the DNS." +edition = "2021" +build = "build.rs" + +[dependencies] +dnssec-prover = { path = "../", default-features = false, features = ["validation"] } +uniffi = { version = "0.27" } + +[build-dependencies] +uniffi = { version = "0.27", features = [ "build" ] } + +[lib] +crate-type = ["cdylib"] + +[profile.release] +lto = true +codegen-units = 1 diff --git a/uniffi/build.rs b/uniffi/build.rs new file mode 100644 index 0000000..4e14e57 --- /dev/null +++ b/uniffi/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::generate_scaffolding("src/interface.udl").unwrap(); +} diff --git a/uniffi/src/interface.udl b/uniffi/src/interface.udl new file mode 100644 index 0000000..a6f02be --- /dev/null +++ b/uniffi/src/interface.udl @@ -0,0 +1,10 @@ +namespace dnssec_prover { + string verify_byte_stream(bytes stream, string name_to_resolve); + ProofBuilder? init_proof_builder(string name, u16 ty); +}; + +interface ProofBuilder { + void process_query_response(bytes response); + bytes? get_next_query(); + bytes? get_unverified_proof(); +}; diff --git a/uniffi/src/lib.rs b/uniffi/src/lib.rs new file mode 100644 index 0000000..109584b --- /dev/null +++ b/uniffi/src/lib.rs @@ -0,0 +1,97 @@ +//! UniFFI-compatible verification wrappers + +uniffi::include_scaffolding!("interface"); + +use dnssec_prover::ser::parse_rr_stream; +use dnssec_prover::validation::{verify_rr_stream, ValidationError}; +use dnssec_prover::rr::Name; +use dnssec_prover::query::ProofBuilder as NativeProofBuilder; +use dnssec_prover::query::{QueryBuf}; + +use std::collections::VecDeque; +use std::fmt::Write; +use std::sync::{Arc, Mutex}; + +pub struct ProofBuilder(Mutex<(NativeProofBuilder, VecDeque)>); + +/// Builds a proof builder which can generate a proof for records of the given `ty`pe at the given +/// `name`. +/// +/// After calling this [`get_next_query`] should be called to fetch the initial query. +pub fn init_proof_builder(mut name: String, ty: u16) -> Option> { + if !name.ends_with('.') { name.push('.'); } + if let Ok(qname) = name.try_into() { + let (builder, initial_query) = NativeProofBuilder::new(&qname, ty); + let mut queries = VecDeque::with_capacity(4); + queries.push_back(initial_query); + Some(Arc::new(ProofBuilder(Mutex::new((builder, queries))))) + } else { + None + } +} + +impl ProofBuilder { + /// Processes a response to a query previously fetched from [`get_next_query`]. + /// + /// After calling this, [`get_next_query`] should be called until pending queries are exhausted and + /// no more pending queries exist, at which point [`get_unverified_proof`] should be called. + pub fn process_query_response(&self, response: Vec) { + if response.len() < u16::MAX as usize { + let mut answer = QueryBuf::new_zeroed(response.len() as u16); + answer.copy_from_slice(&response); + let mut us = self.0.lock().unwrap(); + if let Ok(queries) = us.0.process_response(&answer) { + for query in queries { + us.1.push_back(query); + } + } + } + } + + /// Gets the next query (if any) that should be sent to the resolver for the given proof builder. + /// + /// Once the resolver responds [`process_query_response`] should be called with the response. + pub fn get_next_query(&self) -> Option> { + if let Some(query) = self.0.lock().unwrap().1.pop_front() { + Some(query.into_vec()) + } else { + None + } + } + + /// Gets the final, unverified, proof once all queries fetched via [`get_next_query`] have + /// completed and their responses passed to [`process_query_response`]. + pub fn get_unverified_proof(&self) -> Option> { + self.0.lock().unwrap().0.clone().finish_proof().ok().map(|(proof, _ttl)| proof) + } +} + +/// Verifies an RFC 9102-formatted proof and returns verified records matching the given name +/// (resolving any C/DNAMEs as required). +pub fn verify_byte_stream(stream: Vec, name_to_resolve: String) -> String { + let name = match Name::try_from(name_to_resolve) { + Ok(name) => name, + Err(()) => return "{\"error\":\"Bad name to resolve\"}".to_string(), + }; + match do_verify_byte_stream(stream, name) { + Ok(r) => r, + Err(e) => format!("{{\"error\":\"{:?}\"}}", e), + } +} + +fn do_verify_byte_stream(stream: Vec, name_to_resolve: Name) -> Result { + let rrs = parse_rr_stream(&stream).map_err(|()| ValidationError::Invalid)?; + let verified_rrs = verify_rr_stream(&rrs)?; + let resolved_rrs = verified_rrs.resolve_name(&name_to_resolve); + let mut resp = String::new(); + write!(&mut resp, "{}", + format_args!("{{\"valid_from\": {}, \"expires\": {}, \"max_cache_ttl\": {}, \"verified_rrs\": [", + verified_rrs.valid_from, verified_rrs.expires, verified_rrs.max_cache_ttl) + ).expect("Write to a String shouldn't fail"); + for (idx, rr) in resolved_rrs.iter().enumerate() { + write!(&mut resp, "{}{}", if idx != 0 { ", " } else { "" }, rr.json()) + .expect("Write to a String shouldn't fail"); + } + resp += "]}"; + Ok(resp) +} -- 2.39.5