File: 2002_hickory-resolver.patch

package info (click to toggle)
rust-rustls 0.23.35%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,460 kB
  • sloc: sh: 199; python: 181; makefile: 11
file content (394 lines) | stat: -rw-r--r-- 14,581 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
Description: avoid testing-only too-old-in-Debian crate hickory-resolver
Author: Jonas Smedegaard <dr@jones.dk>
Bug-Debian: https://bugs.debian.org/1111401
Forwarded: not-needed
Last-Update: 2025-09-24
---
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -9,7 +9,6 @@
 [dependencies]
 clap = { workspace = true }
 env_logger = { workspace = true }
-hickory-resolver = { workspace = true }
 log = { workspace = true }
 mio = { workspace = true }
 rcgen = { workspace = true }
--- a/examples/src/bin/ech-client.rs
+++ /dev/null
@@ -1,284 +0,0 @@
-//! This is a simple example demonstrating how to use Encrypted Client Hello (ECH) with
-//! rustls and hickory-dns.
-//!
-//! Note that `unwrap()` is used to deal with networking errors; this is not something
-//! that is sensible outside of example code.
-//!
-//! It should be invoked providing the outer hostname you will make the initial connection
-//! to, and then the inner hostname being protected with ECH. Example usage:
-//! ```text
-//! cargo run --package rustls-examples --bin ech-client -- \
-//!   --host min-ng.test.defo.ie \
-//!   --path "echstat.php?format=json" \
-//!    public.test.defo.ie \
-//!    min-ng.test.defo.ie
-//! ```
-//!
-//! This will perform a DNS-over-HTTPS lookup for the "min-ng.test.defo.ie" server's ECH config.
-//!
-//! Afterward, a TLS connection will be made to "public.test.defo.ie" using the public name
-//! specified in the ECH config as the outer client hello's SNI. The protected inner client
-//! hello's encrypted SNI will be "min-ng.test.defo.ie".
-//!
-//! Once TLS with ECH is negotiated, an HTTP request for Host: "min-ng.test.defo.ie" and the
-//! path "echstat.php?format=json" will be made.
-//!
-//! You should observe JSON output that contains the key/value:
-//! ```
-//! "SSL_ECH_STATUS": "success"
-//! ```
-
-use std::error::Error;
-use std::fs;
-use std::io::{BufReader, Read, Write, stdout};
-use std::net::{TcpStream, ToSocketAddrs};
-use std::sync::Arc;
-
-use clap::Parser;
-use hickory_resolver::config::ResolverConfig;
-use hickory_resolver::name_server::TokioConnectionProvider;
-use hickory_resolver::proto::rr::rdata::svcb::{SvcParamKey, SvcParamValue};
-use hickory_resolver::proto::rr::{RData, RecordType};
-use hickory_resolver::{ResolveError, Resolver, TokioResolver};
-use log::trace;
-use rustls::RootCertStore;
-use rustls::client::{EchConfig, EchGreaseConfig, EchMode, EchStatus};
-use rustls::crypto::aws_lc_rs;
-use rustls::crypto::aws_lc_rs::hpke::ALL_SUPPORTED_SUITES;
-use rustls::crypto::hpke::Hpke;
-use rustls::pki_types::pem::PemObject;
-use rustls::pki_types::{CertificateDer, EchConfigListBytes, ServerName};
-use rustls_native_certs::load_native_certs;
-
-#[tokio::main]
-async fn main() -> Result<(), Box<dyn Error>> {
-    let args = Args::parse();
-
-    let server_ech_configs = match (args.grease, args.ech_config) {
-        (true, Some(_)) => return Err("cannot specify both --grease and --ech-config".into()),
-        (true, None) => {
-            Vec::new() // Force the use of the GREASE ext by skipping ECH config lookup
-        }
-        (false, Some(path)) => {
-            vec![read_ech(&path)?]
-        }
-        (false, None) => {
-            // Find raw ECH configs using DNS-over-HTTPS with Hickory DNS.
-            let resolver_config = if args.use_cloudflare_dns {
-                ResolverConfig::cloudflare_https()
-            } else {
-                ResolverConfig::google_https()
-            };
-            lookup_ech_configs(
-                &Resolver::builder_with_config(resolver_config, TokioConnectionProvider::default())
-                    .build(),
-                &args.inner_hostname,
-                args.port,
-            )
-            .await?
-        }
-    };
-
-    // NOTE: we defer setting up env_logger and setting the trace default filter level until
-    //       after doing the DNS-over-HTTPS lookup above - we don't want to muddy the output
-    //       with the rustls debug logs from the lookup.
-    env_logger::Builder::new()
-        .parse_filters("trace")
-        .init();
-
-    let ech_mode = match server_ech_configs.is_empty() {
-        false => EchMode::from(
-            server_ech_configs
-                .into_iter()
-                .find_map(|list| EchConfig::new(list, ALL_SUPPORTED_SUITES).ok())
-                .ok_or("no supported ECH configs")?,
-        ),
-        true => {
-            let (public_key, _) = GREASE_HPKE_SUITE.generate_key_pair()?;
-            EchMode::from(EchGreaseConfig::new(GREASE_HPKE_SUITE, public_key))
-        }
-    };
-
-    let root_store = match args.cafile {
-        Some(file) => {
-            let mut root_store = RootCertStore::empty();
-            root_store.add_parsable_certificates(
-                CertificateDer::pem_file_iter(file)
-                    .expect("Cannot open CA file")
-                    .map(|result| result.unwrap()),
-            );
-            root_store
-        }
-        None => RootCertStore {
-            let mut root_store = rustls::RootCertStore::empty();
-            for cert in load_native_certs().expect("could not load platform certs") {
-                root_store.add(cert)
-                    .expect("could not add certificate");
-            };
-            root_store
-        },
-    };
-
-    // Construct a rustls client config with a custom provider, and ECH enabled.
-    let mut config =
-        rustls::ClientConfig::builder_with_provider(aws_lc_rs::default_provider().into())
-            .with_ech(ech_mode)?
-            .with_root_certificates(root_store)
-            .with_no_client_auth();
-
-    // Allow using SSLKEYLOGFILE.
-    config.key_log = Arc::new(rustls::KeyLogFile::new());
-    let config = Arc::new(config);
-
-    // The "inner" SNI that we're really trying to reach.
-    let server_name: ServerName<'static> = args.inner_hostname.clone().try_into()?;
-
-    for i in 0..args.num_reqs {
-        trace!("\nRequest {} of {}", i + 1, args.num_reqs);
-        let mut conn = rustls::ClientConnection::new(config.clone(), server_name.clone())?;
-        // The "outer" server that we're connecting to.
-        let sock_addr = (args.outer_hostname.as_str(), args.port)
-            .to_socket_addrs()?
-            .next()
-            .ok_or("cannot resolve hostname")?;
-        let mut sock = TcpStream::connect(sock_addr)?;
-        let mut tls = rustls::Stream::new(&mut conn, &mut sock);
-
-        let request = format!(
-            "GET /{} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\nAccept-Encoding: identity\r\n\r\n",
-            args.path,
-            args.host
-                .as_ref()
-                .unwrap_or(&args.inner_hostname),
-        );
-        dbg!(&request);
-        tls.write_all(request.as_bytes())?;
-        assert!(!tls.conn.is_handshaking());
-        assert_eq!(
-            tls.conn.ech_status(),
-            match args.grease {
-                true => EchStatus::Grease,
-                false => EchStatus::Accepted,
-            }
-        );
-        let mut plaintext = Vec::new();
-        tls.read_to_end(&mut plaintext)?;
-        stdout().write_all(&plaintext)?;
-    }
-    Ok(())
-}
-
-/// Connects to the TLS server at hostname:PORT.  The default PORT
-/// is 443. If an ECH config can be fetched for hostname using
-/// DNS-over-HTTPS, ECH is enabled. Otherwise, a placeholder ECH
-/// extension is sent for anti-ossification testing.
-///
-/// Example:
-///   ech-client --host defo.ie defo.ie www.defo.ie
-#[derive(Debug, Parser)]
-#[clap(version)]
-struct Args {
-    /// Connect to this TCP port.
-    #[clap(short, long, default_value = "443")]
-    port: u16,
-
-    /// Read root certificates from this file.
-    ///
-    /// If --cafile is not supplied, a built-in set of CA certificates
-    /// are used from the webpki-roots crate.
-    #[clap(long)]
-    cafile: Option<String>,
-
-    /// HTTP GET this PATH.
-    #[clap(long, default_value = "ech-check.php")]
-    path: String,
-
-    /// HTTP HOST to use for GET request (defaults to value of inner-hostname).
-    #[clap(long)]
-    host: Option<String>,
-
-    /// Use Google DNS for the DNS-over-HTTPS lookup (default).
-    #[clap(long, group = "dns")]
-    use_google_dns: bool,
-    /// Use Cloudflare DNS for the DNS-over-HTTPS lookup.
-    #[clap(long, group = "dns")]
-    use_cloudflare_dns: bool,
-
-    /// Skip looking up an ECH config and send a GREASE placeholder.
-    #[clap(long)]
-    grease: bool,
-
-    /// Skip looking up an ECH config and read it from the provided file (in binary TLS encoding).
-    #[clap(long)]
-    ech_config: Option<String>,
-
-    /// Number of requests to make.
-    #[clap(long, default_value = "1")]
-    num_reqs: usize,
-
-    /// Outer hostname.
-    outer_hostname: String,
-
-    /// Inner hostname.
-    inner_hostname: String,
-}
-
-/// Collect up all `EchConfigListBytes` found in the HTTPS record(s) for a given domain name/port.
-///
-/// The domain name should be the **inner** name used for Encrypted Client Hello (ECH). The
-/// lookup is done using DNS-over-HTTPS to protect that inner name from being disclosed in
-/// plaintext ahead of the TLS handshake that negotiates ECH for the inner name.
-///
-/// Returns an empty vec if no HTTPS records with ECH configs are found.
-// TODO(@cpu): consider upstreaming to hickory-dns
-async fn lookup_ech_configs(
-    resolver: &TokioResolver,
-    domain: &str,
-    port: u16,
-) -> Result<Vec<EchConfigListBytes<'static>>, ResolveError> {
-    // For non-standard ports, lookup the ECHConfig using port-prefix naming
-    // See: https://datatracker.ietf.org/doc/html/rfc9460#section-9.1
-    let qname_to_lookup = match port {
-        443 => domain.to_owned(),
-        port => format!("_{port}._https.{domain}"),
-    };
-
-    let lookup = resolver
-        .lookup(qname_to_lookup, RecordType::HTTPS)
-        .await?;
-
-    let mut ech_config_lists = Vec::new();
-    for r in lookup.record_iter() {
-        let RData::HTTPS(svcb) = r.data() else {
-            continue;
-        };
-
-        ech_config_lists.extend(
-            svcb.svc_params()
-                .iter()
-                .find_map(|sp| match sp {
-                    (SvcParamKey::EchConfigList, SvcParamValue::EchConfigList(e)) => {
-                        Some(EchConfigListBytes::from(e.clone().0))
-                    }
-                    _ => None,
-                }),
-        )
-    }
-
-    Ok(ech_config_lists)
-}
-
-fn read_ech(path: &str) -> Result<EchConfigListBytes<'static>, Box<dyn Error>> {
-    let file = fs::File::open(path).map_err(|err| format!("cannot open ECH file {path}: {err}"))?;
-    let mut reader = BufReader::new(file);
-    let mut bytes = Vec::new();
-    reader
-        .read_to_end(&mut bytes)
-        .map_err(|err| format!("cannot read ECH file {path}: {err}"))?;
-    Ok(EchConfigListBytes::from(bytes))
-}
-
-/// A HPKE suite to use for GREASE ECH.
-///
-/// A real implementation should vary this suite across all of the suites that are supported.
-static GREASE_HPKE_SUITE: &dyn Hpke = aws_lc_rs::hpke::DH_KEM_X25519_HKDF_SHA256_AES_128;
--- a/connect-tests/Cargo.toml
+++ b/connect-tests/Cargo.toml
@@ -10,7 +10,6 @@
 rustls = { path = "../rustls", features = ["logging"] }
 
 [dev-dependencies]
-hickory-resolver = { workspace = true }
 regex = { workspace = true }
 ring = { workspace = true }
 tokio = { workspace = true }
--- a/connect-tests/tests/ech.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-mod ech_config {
-    use hickory_resolver::config::ResolverConfig;
-    use hickory_resolver::name_server::TokioConnectionProvider;
-    use hickory_resolver::proto::rr::rdata::svcb::{SvcParamKey, SvcParamValue};
-    use hickory_resolver::proto::rr::{RData, RecordType};
-    use hickory_resolver::{Resolver, TokioResolver};
-    use rustls::internal::msgs::codec::{Codec, Reader};
-    use rustls::internal::msgs::handshake::EchConfigPayload;
-    use rustls::pki_types::EchConfigListBytes;
-
-    #[tokio::test]
-    async fn cloudflare() {
-        test_deserialize_ech_config_list("research.cloudflare.com").await;
-    }
-
-    #[tokio::test]
-    async fn defo_ie() {
-        test_deserialize_ech_config_list("defo.ie").await;
-    }
-
-    #[tokio::test]
-    async fn tls_ech_dev() {
-        test_deserialize_ech_config_list("tls-ech.dev").await;
-    }
-
-    /// Lookup the ECH config list for a domain and deserialize it.
-    async fn test_deserialize_ech_config_list(domain: &str) {
-        let resolver = Resolver::builder_with_config(
-            ResolverConfig::google_https(),
-            TokioConnectionProvider::default(),
-        )
-        .build();
-        let tls_encoded_list = lookup_ech(&resolver, domain).await;
-        let parsed_configs = Vec::<EchConfigPayload>::read(&mut Reader::init(&tls_encoded_list))
-            .expect("failed to deserialize ECH config list");
-        assert!(!parsed_configs.is_empty());
-        assert!(
-            parsed_configs
-                .iter()
-                .all(|config| matches!(config, EchConfigPayload::V18(_)))
-        );
-    }
-
-    /// Use `resolver` to make an HTTPS record type query for `domain`, returning the
-    /// first SvcParam EchConfig value found, panicking if none are returned.
-    async fn lookup_ech(resolver: &TokioResolver, domain: &str) -> EchConfigListBytes<'static> {
-        resolver
-            .lookup(domain, RecordType::HTTPS)
-            .await
-            .expect("failed to lookup HTTPS record type")
-            .record_iter()
-            .find_map(|r| match r.data() {
-                RData::HTTPS(svcb) => svcb
-                    .svc_params()
-                    .iter()
-                    .find_map(|sp| match sp {
-                        (SvcParamKey::EchConfigList, SvcParamValue::EchConfigList(e)) => {
-                            Some(e.clone().0)
-                        }
-                        _ => None,
-                    }),
-                _ => None,
-            })
-            .expect("missing expected HTTPS SvcParam EchConfig record")
-            .into()
-    }
-}
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -55,7 +55,6 @@
 env_logger = "0.11"
 hashbrown = { version = ">= 0.15, <= 0.16", default-features = false, features = ["default-hasher", "inline-more"] }
 hex = "0.4"
-hickory-resolver = { version = "0.25", features = ["https-ring", "rustls-platform-verifier"] }
 hmac = "0.12"
 hpke-rs = "0.3"
 hpke-rs-crypto = "0.3"