hickory-proto-0.24.0/.cargo_vcs_info.json0000644000000001520000000000100137260ustar { "git": { "sha1": "408d0baca080d1b201cd33e616dc4abd160ef6c0" }, "path_in_vcs": "crates/proto" }hickory-proto-0.24.0/Cargo.toml0000644000000124200000000000100117250ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.67.0" name = "hickory-proto" version = "0.24.0" authors = ["The contributors to Hickory DNS"] description = """ Hickory DNS is a safe and secure DNS library. This is the foundational DNS protocol library for all Hickory DNS projects. """ homepage = "https://hickory-dns.org/" documentation = "https://docs.rs/hickory-proto" readme = "README.md" keywords = [ "DNS", "BIND", "dig", "named", "dnssec", ] categories = ["network-programming"] license = "MIT OR Apache-2.0" repository = "https://github.com/hickory-dns/hickory-dns" [package.metadata.docs.rs] all-features = true default-target = "x86_64-unknown-linux-gnu" rustdoc-args = [ "--cfg", "docsrs", ] targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc", ] [lib] name = "hickory_proto" path = "src/lib.rs" [dependencies.async-trait] version = "0.1.43" [dependencies.backtrace] version = "0.3.50" optional = true [dependencies.bytes] version = "1" optional = true [dependencies.cfg-if] version = "1" [dependencies.data-encoding] version = "2.2.0" [dependencies.enum-as-inner] version = "0.6" [dependencies.futures-channel] version = "0.3.5" features = ["std"] default-features = false [dependencies.futures-io] version = "0.3.5" features = ["std"] default-features = false [dependencies.futures-util] version = "0.3.5" features = ["std"] default-features = false [dependencies.h2] version = "0.3.0" features = ["stream"] optional = true [dependencies.h3] version = "0.0.2" optional = true [dependencies.h3-quinn] version = "0.0.3" optional = true [dependencies.http] version = "0.2" optional = true [dependencies.idna] version = "0.4.0" [dependencies.ipnet] version = "2.3.0" [dependencies.js-sys] version = "0.3.44" optional = true [dependencies.native-tls] version = "0.2" optional = true [dependencies.once_cell] version = "1.18.0" [dependencies.openssl] version = "0.10.55" features = [ "v102", "v110", ] optional = true [dependencies.quinn] version = "0.10" features = [ "log", "runtime-tokio", "tls-rustls", ] optional = true default-features = false [dependencies.rand] version = "0.8" [dependencies.ring] version = "0.16" features = ["std"] optional = true [dependencies.rustls] version = "0.21.6" optional = true [dependencies.rustls-native-certs] version = "0.6.3" optional = true [dependencies.rustls-pemfile] version = "1.0.0" optional = true [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dependencies.socket2] version = "0.5" optional = true [dependencies.thiserror] version = "1.0.20" [dependencies.tinyvec] version = "1.1.1" features = ["alloc"] [dependencies.tokio] version = "1.21" features = ["io-util"] optional = true [dependencies.tokio-native-tls] version = "0.3.0" optional = true [dependencies.tokio-openssl] version = "0.6.0" optional = true [dependencies.tokio-rustls] version = "0.24.0" features = ["early-data"] optional = true [dependencies.tracing] version = "0.1.30" [dependencies.url] version = "2.4.0" [dependencies.wasm-bindgen-crate] version = "0.2.58" optional = true package = "wasm-bindgen" [dependencies.webpki-roots] version = "0.25.0" optional = true [dev-dependencies.futures-executor] version = "0.3.5" features = ["std"] default-features = false [dev-dependencies.openssl] version = "0.10.55" features = [ "v102", "v110", ] [dev-dependencies.tokio] version = "1.21" features = [ "rt", "time", "macros", ] [dev-dependencies.tracing-subscriber] version = "0.3" features = [ "std", "fmt", "env-filter", ] [features] backtrace = ["dep:backtrace"] default = ["tokio-runtime"] dns-over-h3 = [ "h3", "h3-quinn", "quinn", "http", "dns-over-quic", ] dns-over-https = [ "bytes", "h2", "http", "dns-over-rustls", "tokio-runtime", ] dns-over-https-rustls = ["dns-over-https"] dns-over-native-tls = [ "dns-over-tls", "native-tls", "tokio-native-tls", "tokio-runtime", ] dns-over-openssl = [ "dns-over-tls", "openssl", "tokio-openssl", "tokio-runtime", ] dns-over-quic = [ "quinn", "rustls/quic", "dns-over-rustls", "bytes", "tokio-runtime", ] dns-over-rustls = [ "dns-over-tls", "rustls", "rustls-pemfile", "tokio-rustls", "tokio-runtime", ] dns-over-tls = [] dnssec = [] dnssec-openssl = [ "dnssec", "openssl", ] dnssec-ring = [ "dnssec", "ring", ] mdns = ["socket2/all"] native-certs = ["dep:rustls-native-certs"] serde-config = [ "serde", "url/serde", ] testing = [] text-parsing = [] tokio-runtime = [ "tokio/net", "tokio/rt", "tokio/time", "tokio/rt-multi-thread", ] wasm-bindgen = [ "wasm-bindgen-crate", "js-sys", ] [badges.codecov] branch = "main" repository = "hickory-dns/hickory-dns" service = "github" [badges.maintenance] status = "actively-developed" hickory-proto-0.24.0/Cargo.toml.orig000064400000000000000000000112131046102023000154050ustar 00000000000000[package] name = "hickory-proto" # A short blurb about the package. This is not rendered in any format when # uploaded to crates.io (aka this is not markdown) description = """ Hickory DNS is a safe and secure DNS library. This is the foundational DNS protocol library for all Hickory DNS projects. """ # These URLs point to more information about the repository documentation = "https://docs.rs/hickory-proto" # This points to a file in the repository (relative to this Cargo.toml). The # contents of this file are stored and indexed in the registry. readme = "README.md" version.workspace = true authors.workspace = true edition.workspace = true rust-version.workspace = true homepage.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true license.workspace = true [badges] #github-actions = { repository = "bluejekyll/hickory", branch = "main", workflow = "test" } codecov = { repository = "hickory-dns/hickory-dns", branch = "main", service = "github" } maintenance = { status = "actively-developed" } [features] dns-over-tls = [] dns-over-rustls = [ "dns-over-tls", "rustls", "rustls-pemfile", "tokio-rustls", "tokio-runtime", ] dns-over-native-tls = [ "dns-over-tls", "native-tls", "tokio-native-tls", "tokio-runtime", ] dns-over-openssl = ["dns-over-tls", "openssl", "tokio-openssl", "tokio-runtime"] dns-over-https-rustls = ["dns-over-https"] dns-over-https = ["bytes", "h2", "http", "dns-over-rustls", "tokio-runtime"] dns-over-quic = [ "quinn", "rustls/quic", "dns-over-rustls", "bytes", "tokio-runtime", ] dns-over-h3 = ["h3", "h3-quinn", "quinn", "http", "dns-over-quic"] native-certs = ["dep:rustls-native-certs"] dnssec-openssl = ["dnssec", "openssl"] dnssec-ring = ["dnssec", "ring"] dnssec = [] testing = [] text-parsing = [] tokio-runtime = ["tokio/net", "tokio/rt", "tokio/time", "tokio/rt-multi-thread"] default = ["tokio-runtime"] serde-config = ["serde", "url/serde"] # enables experimental the mDNS (multicast) feature mdns = ["socket2/all"] # WARNING: there is a bug in the mutual tls auth code at the moment see issue #100 # mtls = ["tls"] wasm-bindgen = ["wasm-bindgen-crate", "js-sys"] backtrace = ["dep:backtrace"] [lib] name = "hickory_proto" path = "src/lib.rs" [dependencies] async-trait.workspace = true backtrace = { workspace = true, optional = true } bytes = { workspace = true, optional = true } cfg-if.workspace = true data-encoding.workspace = true enum-as-inner.workspace = true futures-channel = { workspace = true, default-features = false, features = [ "std", ] } futures-io = { workspace = true, default-features = false, features = ["std"] } futures-util = { workspace = true, default-features = false, features = [ "std", ] } h2 = { workspace = true, features = ["stream"], optional = true } h3 = { workspace = true, optional = true } h3-quinn = { workspace = true, optional = true } http = { workspace = true, optional = true } idna.workspace = true ipnet.workspace = true js-sys = { workspace = true, optional = true } native-tls = { workspace = true, optional = true } once_cell.workspace = true openssl = { workspace = true, features = ["v102", "v110"], optional = true } quinn = { workspace = true, optional = true, features = [ "log", "runtime-tokio", "tls-rustls", ] } rand.workspace = true ring = { workspace = true, optional = true, features = ["std"] } rustls = { workspace = true, optional = true } rustls-native-certs = { workspace = true, optional = true } rustls-pemfile = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } socket2 = { workspace = true, optional = true } thiserror.workspace = true tinyvec = { workspace = true, features = ["alloc"] } tracing.workspace = true tokio = { workspace = true, features = ["io-util"], optional = true } tokio-native-tls = { workspace = true, optional = true } tokio-openssl = { workspace = true, optional = true } tokio-rustls = { workspace = true, optional = true, features = ["early-data"] } url.workspace = true wasm-bindgen-crate = { workspace = true, optional = true } webpki-roots = { workspace = true, optional = true } [dev-dependencies] futures-executor = { workspace = true, default-features = false, features = [ "std", ] } openssl = { workspace = true, features = ["v102", "v110"] } tokio = { workspace = true, features = ["rt", "time", "macros"] } tracing-subscriber = { workspace = true, features = [ "std", "fmt", "env-filter", ] } [package.metadata.docs.rs] all-features = true default-target = "x86_64-unknown-linux-gnu" targets = ["x86_64-apple-darwin", "x86_64-pc-windows-msvc"] rustdoc-args = ["--cfg", "docsrs"] hickory-proto-0.24.0/LICENSE-APACHE000064400000000000000000000261401046102023000144470ustar 00000000000000 Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. hickory-proto-0.24.0/LICENSE-MIT000064400000000000000000000021151046102023000141530ustar 00000000000000Copyright (c) 2015 The Hickory DNS Developers Copyright (c) 2017 Google LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hickory-proto-0.24.0/README.md000064400000000000000000000030431046102023000137770ustar 00000000000000# Overview Hickory DNS Proto is the foundational DNS protocol library and implementation for Hickory DNS. Unless you want to manipulate the DNS packets directly, it is likely not the library you want. Please see Hickory DNS [Resolver](https://crates.io/crates/hickory-resolver), [Client](https://crates.io/crates/hickory-client), or [Server](https://crates.io/crates/hickory-server) for higher level interfaces. **NOTICE** This project was rebranded from Trust-DNS to Hickory DNS and has been moved to the https://github.com/hickory-dns/hickory-dns organization and repo, this crate/binary has been moved to [hickory-proto](https://crates.io/crates/hickory-proto), from `0.24` and onward, for prior versions see [trust-dns-proto](https://crates.io/crates/trust-dns-proto). ## Minimum Rust Version The current minimum rustc version for this project is `1.67` ## Versioning Hickory DNS does it's best job to follow semver. Hickory DNS will be promoted to 1.0 upon stabilization of the publicly exposed APIs. This does not mean that Hickory DNS will necessarily break on upgrades between 0.x updates. Whenever possible, old APIs will be deprecated with notes on what replaced those deprecations. Hickory DNS will make a best effort to never break software which depends on it due to API changes, though this can not be guaranteed. Deprecated interfaces will be maintained for at minimum one major release after that in which they were deprecated (where possible), with the exception of the upgrade to 1.0 where all deprecated interfaces will be planned to be removed. hickory-proto-0.24.0/benches/lib.rs000064400000000000000000000120771046102023000152520ustar 00000000000000#![cfg(nightly)] #![feature(test)] extern crate test; use hickory_proto::op::{Header, Message, MessageType, OpCode, ResponseCode}; use hickory_proto::rr::Record; use hickory_proto::serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder}; use test::Bencher; #[bench] fn bench_emit_header(b: &mut Bencher) { let header = Header::new(); b.iter(|| { // we need to create the vector here, otherwise its length is already big enough and the // encoder does not need to resize it let mut bytes = Vec::with_capacity(512); let mut encoder = BinEncoder::new(&mut bytes); header.emit(&mut encoder) }) } // TODO: // This is a bit silly, because everywhere in the codebase, we reserve 512 bytes for the buffer. // But what we want to measure here is the cost of reserving more space, which can happen for big // messages exceeding 512 bytes. A better benchmark would be to emit such a big message. #[bench] fn bench_parse_header_no_reservation(b: &mut Bencher) { let header = Header::new(); b.iter(|| { let mut bytes = Vec::with_capacity(0); let mut encoder = BinEncoder::new(&mut bytes); header.emit(&mut encoder) }) } #[bench] fn bench_parse_header(b: &mut Bencher) { let byte_vec = vec![ 0x01, 0x10, 0xAA, 0x83, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, ]; b.iter(|| { let mut decoder = BinDecoder::new(&byte_vec); Header::read(&mut decoder) }) } #[bench] fn bench_emit_message(b: &mut Bencher) { let mut message = Message::new(); message .set_id(10) .set_message_type(MessageType::Response) .set_op_code(OpCode::Update) .set_authoritative(true) .set_truncated(true) .set_recursion_desired(true) .set_recursion_available(true) .set_authentic_data(true) .set_checking_disabled(true) .set_response_code(ResponseCode::ServFail); message.add_answer(Record::new()); message.add_name_server(Record::new()); message.add_additional(Record::new()); b.iter(|| { let mut byte_vec: Vec = Vec::with_capacity(512); let mut encoder = BinEncoder::new(&mut byte_vec); message.emit(&mut encoder) }) } #[bench] fn bench_emit_message_no_reservation(b: &mut Bencher) { let mut message = Message::new(); message .set_id(10) .set_message_type(MessageType::Response) .set_op_code(OpCode::Update) .set_authoritative(true) .set_truncated(true) .set_recursion_desired(true) .set_recursion_available(true) .set_authentic_data(true) .set_checking_disabled(true) .set_response_code(ResponseCode::ServFail); message.add_answer(Record::new()); message.add_name_server(Record::new()); message.add_additional(Record::new()); b.iter(|| { let mut byte_vec: Vec = Vec::with_capacity(0); let mut encoder = BinEncoder::new(&mut byte_vec); message.emit(&mut encoder) }) } #[bench] fn bench_parse_message(b: &mut Bencher) { let mut message = Message::new(); message .set_id(10) .set_message_type(MessageType::Response) .set_op_code(OpCode::Update) .set_authoritative(true) .set_truncated(true) .set_recursion_desired(true) .set_recursion_available(true) .set_authentic_data(true) .set_checking_disabled(true) .set_response_code(ResponseCode::ServFail); message.add_answer(Record::new()); message.add_name_server(Record::new()); message.add_additional(Record::new()); let mut byte_vec: Vec = Vec::with_capacity(512); { let mut encoder = BinEncoder::new(&mut byte_vec); message.emit(&mut encoder).unwrap(); } b.iter(|| { let mut decoder = BinDecoder::new(&byte_vec); Message::read(&mut decoder) }) } #[bench] fn bench_parse_real_message(b: &mut Bencher) { let bytes = [ 145, 188, 129, 128, 0, 1, 0, 6, 0, 0, 0, 0, 5, 118, 105, 100, 101, 111, 5, 116, 119, 105, 109, 103, 3, 99, 111, 109, 0, 0, 1, 0, 1, 192, 12, 0, 5, 0, 1, 0, 0, 0, 245, 0, 11, 8, 118, 105, 100, 101, 111, 45, 97, 107, 192, 18, 192, 45, 0, 5, 0, 1, 0, 0, 12, 213, 0, 24, 5, 118, 105, 100, 101, 111, 5, 116, 119, 105, 109, 103, 6, 97, 107, 97, 100, 110, 115, 3, 110, 101, 116, 0, 192, 68, 0, 5, 0, 1, 0, 0, 0, 57, 0, 28, 5, 118, 105, 100, 101, 111, 5, 116, 119, 105, 109, 103, 3, 99, 111, 109, 9, 97, 107, 97, 109, 97, 105, 122, 101, 100, 192, 87, 192, 104, 0, 5, 0, 1, 0, 0, 2, 194, 0, 22, 5, 118, 105, 100, 101, 111, 5, 116, 119, 105, 109, 103, 3, 99, 111, 109, 3, 101, 105, 112, 192, 80, 192, 144, 0, 5, 0, 1, 0, 0, 0, 43, 0, 35, 8, 101, 105, 112, 45, 116, 97, 116, 97, 5, 118, 105, 100, 101, 111, 5, 116, 119, 105, 109, 103, 3, 99, 111, 109, 7, 97, 107, 97, 104, 111, 115, 116, 192, 87, 192, 178, 0, 1, 0, 1, 0, 0, 0, 23, 0, 4, 184, 31, 3, 236, ]; b.iter(|| { let mut decoder = BinDecoder::new(&bytes[..]); assert!(Message::read(&mut decoder).is_ok()); }) } hickory-proto-0.24.0/benches/name_benches.rs000064400000000000000000000073631046102023000171150ustar 00000000000000#![cfg(nightly)] #![feature(test)] extern crate test; use std::cmp::Ordering; use test::Bencher; use hickory_proto::rr::*; #[bench] fn name_cmp_short(b: &mut Bencher) { let name1 = Name::from_ascii("com").unwrap(); let name2 = Name::from_ascii("COM").unwrap(); b.iter(|| { assert_eq!(name1.cmp(&name2), Ordering::Equal); }); } #[bench] fn name_cmp_short_not_eq(b: &mut Bencher) { let name1 = Name::from_ascii("com").unwrap(); let name2 = Name::from_ascii("COM").unwrap(); b.iter(|| { assert_ne!(name1.cmp_case(&name2), Ordering::Equal); }); } #[bench] fn name_cmp_short_case(b: &mut Bencher) { let name1 = Name::from_ascii("com").unwrap(); let name2 = Name::from_ascii("com").unwrap(); b.iter(|| { assert_eq!(name1.cmp_case(&name2), Ordering::Equal); }); } #[bench] fn name_cmp_medium(b: &mut Bencher) { let name1 = Name::from_ascii("www.example.com").unwrap(); let name2 = Name::from_ascii("www.EXAMPLE.com").unwrap(); b.iter(|| { assert_eq!(name1.cmp(&name2), Ordering::Equal); }); } #[bench] fn name_cmp_medium_not_eq(b: &mut Bencher) { let name1 = Name::from_ascii("www.example.com").unwrap(); let name2 = Name::from_ascii("www.EXAMPLE.com").unwrap(); b.iter(|| { assert_ne!(name1.cmp_case(&name2), Ordering::Equal); }); } #[bench] fn name_cmp_medium_case(b: &mut Bencher) { let name1 = Name::from_ascii("www.example.com").unwrap(); let name2 = Name::from_ascii("www.example.com").unwrap(); b.iter(|| { assert_eq!(name1.cmp_case(&name2), Ordering::Equal); }); } #[bench] fn name_cmp_long(b: &mut Bencher) { let name1 = Name::from_ascii("a.crazy.really.long.example.com").unwrap(); let name2 = Name::from_ascii("a.crazy.really.long.EXAMPLE.com").unwrap(); b.iter(|| { assert_eq!(name1.cmp(&name2), Ordering::Equal); }); } #[bench] fn name_cmp_long_not_eq(b: &mut Bencher) { let name1 = Name::from_ascii("a.crazy.really.long.example.com").unwrap(); let name2 = Name::from_ascii("a.crazy.really.long.EXAMPLE.com").unwrap(); b.iter(|| { assert_ne!(name1.cmp_case(&name2), Ordering::Equal); }); } #[bench] fn name_cmp_long_case(b: &mut Bencher) { let name1 = Name::from_ascii("a.crazy.really.long.example.com").unwrap(); let name2 = Name::from_ascii("a.crazy.really.long.example.com").unwrap(); b.iter(|| { assert_eq!(name1.cmp_case(&name2), Ordering::Equal); }); } #[bench] fn name_to_lower_short(b: &mut Bencher) { let name1 = Name::from_ascii("COM").unwrap(); b.iter(|| { let lower = name1.to_lowercase(); assert_eq!(lower.num_labels(), 1); }); } #[bench] fn name_to_lower_medium(b: &mut Bencher) { let name1 = Name::from_ascii("example.COM").unwrap(); b.iter(|| { let lower = name1.to_lowercase(); assert_eq!(lower.num_labels(), 2); }); } #[bench] fn name_to_lower_long(b: &mut Bencher) { let name1 = Name::from_ascii("www.EXAMPLE.com").unwrap(); b.iter(|| { let lower = name1.to_lowercase(); assert_eq!(lower.num_labels(), 3); }); } #[bench] fn name_no_lower_short(b: &mut Bencher) { let name1 = Name::from_ascii("com").unwrap(); b.iter(|| { let lower = name1.to_lowercase(); assert_eq!(lower.num_labels(), 1); }); } #[bench] fn name_no_lower_medium(b: &mut Bencher) { let name1 = Name::from_ascii("example.com").unwrap(); b.iter(|| { let lower = name1.to_lowercase(); assert_eq!(lower.num_labels(), 2); }); } #[bench] fn name_no_lower_long(b: &mut Bencher) { let name1 = Name::from_ascii("www.example.com").unwrap(); b.iter(|| { let lower = name1.to_lowercase(); assert_eq!(lower.num_labels(), 3); }); } hickory-proto-0.24.0/src/error.rs000064400000000000000000000535101046102023000150120ustar 00000000000000// Copyright 2015-2020 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Error types for the crate #![deny(missing_docs)] use std::{fmt, io, sync}; #[cfg(feature = "backtrace")] #[cfg_attr(docsrs, doc(cfg(feature = "backtrace")))] pub use backtrace::Backtrace as ExtBacktrace; use enum_as_inner::EnumAsInner; #[cfg(feature = "backtrace")] use once_cell::sync::Lazy; use thiserror::Error; use crate::op::Header; #[cfg(feature = "dnssec")] use crate::rr::dnssec::rdata::tsig::TsigAlgorithm; use crate::rr::{Name, RecordType}; use crate::serialize::binary::DecodeError; /// Boolean for checking if backtrace is enabled at runtime #[cfg(feature = "backtrace")] #[cfg_attr(docsrs, doc(cfg(feature = "backtrace")))] pub static ENABLE_BACKTRACE: Lazy = Lazy::new(|| { use std::env; let bt = env::var("RUST_BACKTRACE"); matches!(bt.as_ref().map(|s| s as &str), Ok("full") | Ok("1")) }); /// Generate a backtrace /// /// If RUST_BACKTRACE is 1 or full then this will return Some(Backtrace), otherwise, NONE. #[cfg(feature = "backtrace")] #[cfg_attr(docsrs, doc(cfg(feature = "backtrace")))] #[macro_export] macro_rules! trace { () => {{ use $crate::error::ExtBacktrace as Backtrace; if *$crate::error::ENABLE_BACKTRACE { Some(Backtrace::new()) } else { None } }}; } /// An alias for results returned by functions of this crate pub type ProtoResult = ::std::result::Result; /// The error kind for errors that get returned in the crate #[derive(Debug, EnumAsInner, Error)] #[non_exhaustive] pub enum ProtoErrorKind { /// Query count is not one #[error("there should only be one query per request, got: {0}")] BadQueryCount(usize), /// The underlying resource is too busy /// /// This is a signal that an internal resource is too busy. The intended action should be tried /// again, ideally after waiting for a little while for the situation to improve. Alternatively, /// the action could be tried on another resource (for example, in a name server pool). #[error("resource too busy")] Busy, /// An error caused by a canceled future #[error("future was canceled: {0:?}")] Canceled(futures_channel::oneshot::Canceled), /// Character data length exceeded the limit #[error("char data length exceeds {max}: {len}")] CharacterDataTooLong { /// Specified maximum max: usize, /// Actual length len: usize, }, /// Overlapping labels #[error("overlapping labels name {label} other {other}")] LabelOverlapsWithOther { /// Start of the label that is overlaps label: usize, /// Start of the other label other: usize, }, /// DNS protocol version doesn't have the expected version 3 #[error("dns key value unknown, must be 3: {0}")] DnsKeyProtocolNot3(u8), /// A domain name was too long #[error("name label data exceed 255: {0}")] DomainNameTooLong(usize), /// EDNS resource record label is not the root label, although required #[error("edns resource record label must be the root label (.): {0}")] EdnsNameNotRoot(crate::rr::Name), /// Format error in Message Parsing #[error("message format error: {error}")] FormError { /// Header of the bad Message header: Header, /// Error that occured while parsing the Message error: Box, }, /// An HMAC failed to verify #[error("hmac validation failure")] HmacInvalid(), /// The length of rdata read was not as expected #[error("incorrect rdata length read: {read} expected: {len}")] IncorrectRDataLengthRead { /// The amount of read data read: usize, /// The expected length of the data len: usize, }, /// Label bytes exceeded the limit of 63 #[error("label bytes exceed 63: {0}")] LabelBytesTooLong(usize), /// Label bytes exceeded the limit of 63 #[error("label points to data not prior to idx: {idx} ptr: {ptr}")] PointerNotPriorToLabel { /// index of the label containing this pointer idx: usize, /// location to which the pointer is directing ptr: u16, }, /// The maximum buffer size was exceeded #[error("maximum buffer size exceeded: {0}")] MaxBufferSizeExceeded(usize), /// An error with an arbitrary message, referenced as &'static str #[error("{0}")] Message(&'static str), /// An error with an arbitrary message, stored as String #[error("{0}")] Msg(String), /// No error was specified #[error("no error specified")] NoError, /// Not all records were able to be written #[error("not all records could be written, wrote: {count}")] NotAllRecordsWritten { /// Number of records that were written before the error count: usize, }, /// Missing rrsigs #[error("rrsigs are not present for record set name: {name} record_type: {record_type}")] RrsigsNotPresent { /// The record set name name: Name, /// The record type record_type: RecordType, }, /// An unknown algorithm type was found #[error("algorithm type value unknown: {0}")] UnknownAlgorithmTypeValue(u8), /// An unknown dns class was found #[error("dns class string unknown: {0}")] UnknownDnsClassStr(String), /// An unknown dns class value was found #[error("dns class value unknown: {0}")] UnknownDnsClassValue(u16), /// An unknown record type string was found #[error("record type string unknown: {0}")] UnknownRecordTypeStr(String), /// An unknown record type value was found #[error("record type value unknown: {0}")] UnknownRecordTypeValue(u16), /// An unrecognized label code was found #[error("unrecognized label code: {0:b}")] UnrecognizedLabelCode(u8), /// Unrecognized nsec3 flags were found #[error("nsec3 flags should be 0b0000000*: {0:b}")] UnrecognizedNsec3Flags(u8), /// Unrecognized csync flags were found #[error("csync flags should be 0b000000**: {0:b}")] UnrecognizedCsyncFlags(u16), // foreign /// An error got returned from IO #[error("io error: {0}")] Io(io::Error), /// Any sync poised error #[error("lock poisoned error")] Poisoned, /// A ring error #[error("ring error: {0}")] Ring(#[from] Unspecified), /// An ssl error #[error("ssl error: {0}")] SSL(#[from] SslErrorStack), /// A tokio timer error #[error("timer error")] Timer, /// A request timed out #[error("request timed out")] Timeout, /// Tsig key verification failed #[error("Tsig key wrong key error")] TsigWrongKey, /// Tsig unsupported mac algorithm /// Supported algorithm documented in `TsigAlgorithm::supported` function. #[cfg(feature = "dnssec")] #[error("Tsig unsupported mac algorithm")] TsigUnsupportedMacAlgorithm(TsigAlgorithm), /// An url parsing error #[error("url parsing error")] UrlParsing(#[from] url::ParseError), /// A utf8 parsing error #[error("error parsing utf8 string")] Utf8(#[from] std::str::Utf8Error), /// A utf8 parsing error #[error("error parsing utf8 string")] FromUtf8(#[from] std::string::FromUtf8Error), /// An int parsing error #[error("error parsing int")] ParseInt(#[from] std::num::ParseIntError), /// A Quinn (Quic) connection error occurred #[cfg(feature = "quinn")] #[error("error creating quic connection: {0}")] QuinnConnect(#[from] quinn::ConnectError), /// A Quinn (QUIC) connection error occurred #[cfg(feature = "quinn")] #[error("error with quic connection: {0}")] QuinnConnection(#[from] quinn::ConnectionError), /// A Quinn (QUIC) write error occurred #[cfg(feature = "quinn")] #[error("error writing to quic connection: {0}")] QuinnWriteError(#[from] quinn::WriteError), /// A Quinn (QUIC) read error occurred #[cfg(feature = "quinn")] #[error("error writing to quic read: {0}")] QuinnReadError(#[from] quinn::ReadExactError), /// A Quinn (QUIC) configuration error occurred #[cfg(feature = "quinn")] #[error("error constructing quic configuration: {0}")] QuinnConfigError(#[from] quinn::ConfigError), /// Unknown QUIC stream used #[cfg(feature = "quinn")] #[error("an unknown quic stream was used")] QuinnUnknownStreamError, /// A quic message id should always be 0 #[cfg(feature = "quinn")] #[error("quic messages should always be 0, got: {0}")] QuicMessageIdNot0(u16), /// A Rustls error occurred #[cfg(feature = "rustls")] #[error("rustls construction error: {0}")] RustlsError(#[from] rustls::Error), /// No valid certificates found in the native root store. #[cfg(all(feature = "native-certs", not(feature = "webpki-roots")))] #[error("no valid certificates found in the native root store")] NativeCerts, } /// The error type for errors that get returned in the crate #[derive(Error, Clone, Debug)] #[non_exhaustive] pub struct ProtoError { /// Kind of error that ocurred pub kind: Box, /// Backtrace to the source of the error #[cfg(feature = "backtrace")] pub backtrack: Option, } impl ProtoError { /// Get the kind of the error pub fn kind(&self) -> &ProtoErrorKind { &self.kind } /// If this is a ProtoErrorKind::Busy pub fn is_busy(&self) -> bool { matches!(*self.kind, ProtoErrorKind::Busy) } pub(crate) fn as_dyn(&self) -> &(dyn std::error::Error + 'static) { self } } impl fmt::Display for ProtoError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { cfg_if::cfg_if! { if #[cfg(feature = "backtrace")] { if let Some(ref backtrace) = self.backtrack { fmt::Display::fmt(&self.kind, f)?; fmt::Debug::fmt(backtrace, f) } else { fmt::Display::fmt(&self.kind, f) } } else { fmt::Display::fmt(&self.kind, f) } } } } impl From for ProtoError where E: Into, { fn from(error: E) -> Self { let kind: ProtoErrorKind = error.into(); Self { kind: Box::new(kind), #[cfg(feature = "backtrace")] backtrack: trace!(), } } } impl From for ProtoError { fn from(err: DecodeError) -> Self { match err { DecodeError::PointerNotPriorToLabel { idx, ptr } => { ProtoErrorKind::PointerNotPriorToLabel { idx, ptr } } DecodeError::LabelBytesTooLong(len) => ProtoErrorKind::LabelBytesTooLong(len), DecodeError::UnrecognizedLabelCode(code) => ProtoErrorKind::UnrecognizedLabelCode(code), DecodeError::DomainNameTooLong(len) => ProtoErrorKind::DomainNameTooLong(len), DecodeError::LabelOverlapsWithOther { label, other } => { ProtoErrorKind::LabelOverlapsWithOther { label, other } } _ => ProtoErrorKind::Msg(err.to_string()), } .into() } } impl From<&'static str> for ProtoError { fn from(msg: &'static str) -> Self { ProtoErrorKind::Message(msg).into() } } impl From for ProtoError { fn from(msg: String) -> Self { ProtoErrorKind::Msg(msg).into() } } impl From for ProtoErrorKind { fn from(e: io::Error) -> Self { match e.kind() { io::ErrorKind::TimedOut => Self::Timeout, _ => Self::Io(e), } } } impl From> for ProtoError { fn from(_e: sync::PoisonError) -> Self { ProtoErrorKind::Poisoned.into() } } impl From for io::Error { fn from(e: ProtoError) -> Self { match *e.kind() { ProtoErrorKind::Timeout => Self::new(io::ErrorKind::TimedOut, e), _ => Self::new(io::ErrorKind::Other, e), } } } impl From for String { fn from(e: ProtoError) -> Self { e.to_string() } } #[cfg(feature = "wasm-bindgen")] #[cfg_attr(docsrs, doc(cfg(feature = "wasm-bindgen")))] impl From for wasm_bindgen_crate::JsValue { fn from(e: ProtoError) -> Self { js_sys::Error::new(&e.to_string()).into() } } impl Clone for ProtoErrorKind { fn clone(&self) -> Self { use self::ProtoErrorKind::*; match *self { BadQueryCount(count) => BadQueryCount(count), Busy => Busy, Canceled(ref c) => Canceled(*c), CharacterDataTooLong { max, len } => CharacterDataTooLong { max, len }, LabelOverlapsWithOther { label, other } => LabelOverlapsWithOther { label, other }, DnsKeyProtocolNot3(protocol) => DnsKeyProtocolNot3(protocol), DomainNameTooLong(len) => DomainNameTooLong(len), EdnsNameNotRoot(ref found) => EdnsNameNotRoot(found.clone()), FormError { header, ref error } => FormError { header, error: error.clone(), }, HmacInvalid() => HmacInvalid(), IncorrectRDataLengthRead { read, len } => IncorrectRDataLengthRead { read, len }, LabelBytesTooLong(len) => LabelBytesTooLong(len), PointerNotPriorToLabel { idx, ptr } => PointerNotPriorToLabel { idx, ptr }, MaxBufferSizeExceeded(max) => MaxBufferSizeExceeded(max), Message(msg) => Message(msg), Msg(ref msg) => Msg(msg.clone()), NoError => NoError, NotAllRecordsWritten { count } => NotAllRecordsWritten { count }, RrsigsNotPresent { ref name, ref record_type, } => RrsigsNotPresent { name: name.clone(), record_type: *record_type, }, UnknownAlgorithmTypeValue(value) => UnknownAlgorithmTypeValue(value), UnknownDnsClassStr(ref value) => UnknownDnsClassStr(value.clone()), UnknownDnsClassValue(value) => UnknownDnsClassValue(value), UnknownRecordTypeStr(ref value) => UnknownRecordTypeStr(value.clone()), UnknownRecordTypeValue(value) => UnknownRecordTypeValue(value), UnrecognizedLabelCode(value) => UnrecognizedLabelCode(value), UnrecognizedNsec3Flags(flags) => UnrecognizedNsec3Flags(flags), UnrecognizedCsyncFlags(flags) => UnrecognizedCsyncFlags(flags), // foreign Io(ref e) => Io(if let Some(raw) = e.raw_os_error() { io::Error::from_raw_os_error(raw) } else { io::Error::from(e.kind()) }), Poisoned => Poisoned, Ring(ref _e) => Ring(Unspecified), SSL(ref e) => Msg(format!("there was an SSL error: {e}")), Timeout => Timeout, Timer => Timer, #[cfg(feature = "dnssec")] TsigUnsupportedMacAlgorithm(ref alg) => TsigUnsupportedMacAlgorithm(alg.clone()), TsigWrongKey => TsigWrongKey, UrlParsing(ref e) => UrlParsing(*e), Utf8(ref e) => Utf8(*e), FromUtf8(ref e) => FromUtf8(e.clone()), ParseInt(ref e) => ParseInt(e.clone()), #[cfg(feature = "quinn")] QuinnConnect(ref e) => QuinnConnect(e.clone()), #[cfg(feature = "quinn")] QuinnConnection(ref e) => QuinnConnection(e.clone()), #[cfg(feature = "quinn")] QuinnWriteError(ref e) => QuinnWriteError(e.clone()), #[cfg(feature = "quinn")] QuicMessageIdNot0(val) => QuicMessageIdNot0(val), #[cfg(feature = "quinn")] QuinnReadError(ref e) => QuinnReadError(e.clone()), #[cfg(feature = "quinn")] QuinnConfigError(ref e) => QuinnConfigError(e.clone()), #[cfg(feature = "quinn")] QuinnUnknownStreamError => QuinnUnknownStreamError, #[cfg(feature = "rustls")] RustlsError(ref e) => RustlsError(e.clone()), #[cfg(all(feature = "native-certs", not(feature = "webpki-roots")))] NativeCerts => NativeCerts, } } } /// A trait marking a type which implements From and /// std::error::Error types as well as Clone + Send pub trait FromProtoError: From + std::error::Error + Clone {} impl FromProtoError for E where E: From + std::error::Error + Clone {} #[cfg(not(feature = "openssl"))] use self::not_openssl::SslErrorStack; #[cfg(not(feature = "ring"))] use self::not_ring::{KeyRejected, Unspecified}; #[cfg(feature = "openssl")] use openssl::error::ErrorStack as SslErrorStack; #[cfg(feature = "ring")] use ring::error::{KeyRejected, Unspecified}; /// An alias for dnssec results returned by functions of this crate pub type DnsSecResult = ::std::result::Result; /// The error kind for dnssec errors that get returned in the crate #[allow(unreachable_pub)] #[derive(Debug, Error)] #[non_exhaustive] pub enum DnsSecErrorKind { /// An error with an arbitrary message, referenced as &'static str #[error("{0}")] Message(&'static str), /// An error with an arbitrary message, stored as String #[error("{0}")] Msg(String), // foreign /// An error got returned by the hickory-proto crate #[error("proto error: {0}")] Proto(#[from] ProtoError), /// A ring error #[error("ring error: {0}")] RingKeyRejected(#[from] KeyRejected), /// A ring error #[error("ring error: {0}")] RingUnspecified(#[from] Unspecified), /// An ssl error #[error("ssl error: {0}")] SSL(#[from] SslErrorStack), /// A request timed out #[error("request timed out")] Timeout, } impl Clone for DnsSecErrorKind { fn clone(&self) -> Self { use DnsSecErrorKind::*; match self { Message(msg) => Message(msg), Msg(ref msg) => Msg(msg.clone()), // foreign Proto(proto) => Proto(proto.clone()), RingKeyRejected(r) => Msg(format!("Ring rejected key: {r}")), RingUnspecified(_r) => RingUnspecified(Unspecified), SSL(ssl) => Msg(format!("SSL had an error: {ssl}")), Timeout => Timeout, } } } /// The error type for dnssec errors that get returned in the crate #[derive(Debug, Clone, Error)] pub struct DnsSecError { kind: DnsSecErrorKind, #[cfg(feature = "backtrace")] backtrack: Option, } impl DnsSecError { /// Get the kind of the error pub fn kind(&self) -> &DnsSecErrorKind { &self.kind } } impl fmt::Display for DnsSecError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { cfg_if::cfg_if! { if #[cfg(feature = "backtrace")] { if let Some(ref backtrace) = self.backtrack { fmt::Display::fmt(&self.kind, f)?; fmt::Debug::fmt(backtrace, f) } else { fmt::Display::fmt(&self.kind, f) } } else { fmt::Display::fmt(&self.kind, f) } } } } impl From for DnsSecError { fn from(kind: DnsSecErrorKind) -> Self { Self { kind, #[cfg(feature = "backtrace")] backtrack: trace!(), } } } impl From<&'static str> for DnsSecError { fn from(msg: &'static str) -> Self { DnsSecErrorKind::Message(msg).into() } } impl From for DnsSecError { fn from(msg: String) -> Self { DnsSecErrorKind::Msg(msg).into() } } impl From for DnsSecError { fn from(e: ProtoError) -> Self { match *e.kind() { ProtoErrorKind::Timeout => DnsSecErrorKind::Timeout.into(), _ => DnsSecErrorKind::from(e).into(), } } } impl From for DnsSecError { fn from(e: KeyRejected) -> Self { DnsSecErrorKind::from(e).into() } } impl From for DnsSecError { fn from(e: Unspecified) -> Self { DnsSecErrorKind::from(e).into() } } impl From for DnsSecError { fn from(e: SslErrorStack) -> Self { DnsSecErrorKind::from(e).into() } } #[doc(hidden)] #[allow(unreachable_pub)] #[cfg(not(feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "openssl"))))] pub mod not_openssl { use std; #[derive(Clone, Copy, Debug)] pub struct SslErrorStack; impl std::fmt::Display for SslErrorStack { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) } } impl std::error::Error for SslErrorStack { fn description(&self) -> &str { "openssl feature not enabled" } } } #[doc(hidden)] #[allow(unreachable_pub)] #[cfg(not(feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub mod not_ring { use std; #[derive(Clone, Copy, Debug)] pub struct KeyRejected; #[derive(Clone, Copy, Debug)] pub struct Unspecified; impl std::fmt::Display for KeyRejected { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) } } impl std::error::Error for KeyRejected { fn description(&self) -> &str { "ring feature not enabled" } } impl std::fmt::Display for Unspecified { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) } } impl std::error::Error for Unspecified { fn description(&self) -> &str { "ring feature not enabled" } } } hickory-proto-0.24.0/src/h2/h2_client_stream.rs000064400000000000000000000661331046102023000174210ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::fmt::{self, Display}; use std::future::Future; use std::io; use std::net::SocketAddr; use std::ops::DerefMut; use std::pin::Pin; use std::str::FromStr; use std::sync::Arc; use std::task::{Context, Poll}; use bytes::{Buf, Bytes, BytesMut}; use futures_util::future::{FutureExt, TryFutureExt}; use futures_util::ready; use futures_util::stream::Stream; use h2::client::{Connection, SendRequest}; use http::header::{self, CONTENT_LENGTH}; use rustls::ClientConfig; use tokio_rustls::{ client::TlsStream as TokioTlsClientStream, Connect as TokioTlsConnect, TlsConnector, }; use tracing::{debug, warn}; use crate::error::ProtoError; use crate::http::Version; use crate::iocompat::AsyncIoStdAsTokio; use crate::op::Message; use crate::tcp::{Connect, DnsTcpStream}; use crate::xfer::{DnsRequest, DnsRequestSender, DnsResponse, DnsResponseStream}; const ALPN_H2: &[u8] = b"h2"; /// A DNS client connection for DNS-over-HTTPS #[derive(Clone)] #[must_use = "futures do nothing unless polled"] pub struct HttpsClientStream { // Corresponds to the dns-name of the HTTPS server name_server_name: Arc, name_server: SocketAddr, h2: SendRequest, is_shutdown: bool, } impl Display for HttpsClientStream { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( formatter, "HTTPS({},{})", self.name_server, self.name_server_name ) } } impl HttpsClientStream { async fn inner_send( h2: SendRequest, message: Bytes, name_server_name: Arc, ) -> Result { let mut h2 = match h2.ready().await { Ok(h2) => h2, Err(err) => { // TODO: make specific error return Err(ProtoError::from(format!("h2 send_request error: {err}"))); } }; // build up the http request let request = crate::http::request::new(Version::Http2, &name_server_name, message.remaining()); let request = request.map_err(|err| ProtoError::from(format!("bad http request: {err}")))?; debug!("request: {:#?}", request); // Send the request let (response_future, mut send_stream) = h2 .send_request(request, false) .map_err(|err| ProtoError::from(format!("h2 send_request error: {err}")))?; send_stream .send_data(message, true) .map_err(|e| ProtoError::from(format!("h2 send_data error: {e}")))?; let mut response_stream = response_future .await .map_err(|err| ProtoError::from(format!("received a stream error: {err}")))?; debug!("got response: {:#?}", response_stream); // get the length of packet let content_length = response_stream .headers() .get(CONTENT_LENGTH) .map(|v| v.to_str()) .transpose() .map_err(|e| ProtoError::from(format!("bad headers received: {e}")))? .map(usize::from_str) .transpose() .map_err(|e| ProtoError::from(format!("bad headers received: {e}")))?; // TODO: what is a good max here? // clamp(512, 4096) says make sure it is at least 512 bytes, and min 4096 says it is at most 4k // just a little protection from malicious actors. let mut response_bytes = BytesMut::with_capacity(content_length.unwrap_or(512).clamp(512, 4096)); while let Some(partial_bytes) = response_stream.body_mut().data().await { let partial_bytes = partial_bytes.map_err(|e| ProtoError::from(format!("bad http request: {e}")))?; debug!("got bytes: {}", partial_bytes.len()); response_bytes.extend(partial_bytes); // assert the length if let Some(content_length) = content_length { if response_bytes.len() >= content_length { break; } } } // assert the length if let Some(content_length) = content_length { if response_bytes.len() != content_length { // TODO: make explicit error type return Err(ProtoError::from(format!( "expected byte length: {}, got: {}", content_length, response_bytes.len() ))); } } // Was it a successful request? if !response_stream.status().is_success() { let error_string = String::from_utf8_lossy(response_bytes.as_ref()); // TODO: make explicit error type return Err(ProtoError::from(format!( "http unsuccessful code: {}, message: {}", response_stream.status(), error_string ))); } else { // verify content type { // in the case that the ContentType is not specified, we assume it's the standard DNS format let content_type = response_stream .headers() .get(header::CONTENT_TYPE) .map(|h| { h.to_str().map_err(|err| { // TODO: make explicit error type ProtoError::from(format!("ContentType header not a string: {err}")) }) }) .unwrap_or(Ok(crate::http::MIME_APPLICATION_DNS))?; if content_type != crate::http::MIME_APPLICATION_DNS { return Err(ProtoError::from(format!( "ContentType unsupported (must be '{}'): '{}'", crate::http::MIME_APPLICATION_DNS, content_type ))); } } }; // and finally convert the bytes into a DNS message let message = Message::from_vec(&response_bytes)?; Ok(DnsResponse::new(message, response_bytes.to_vec())) } } impl DnsRequestSender for HttpsClientStream { /// This indicates that the HTTP message was successfully sent, and we now have the response.RecvStream /// /// If the request fails, this will return the error, and it should be assumed that the Stream portion of /// this will have no date. /// /// ```text /// 5.2. The HTTP Response /// /// An HTTP response with a 2xx status code ([RFC7231] Section 6.3) /// indicates a valid DNS response to the query made in the HTTP request. /// A valid DNS response includes both success and failure responses. /// For example, a DNS failure response such as SERVFAIL or NXDOMAIN will /// be the message in a successful 2xx HTTP response even though there /// was a failure at the DNS layer. Responses with non-successful HTTP /// status codes do not contain DNS answers to the question in the /// corresponding request. Some of these non-successful HTTP responses /// (e.g., redirects or authentication failures) could mean that clients /// need to make new requests to satisfy the original question. /// /// Different response media types will provide more or less information /// from a DNS response. For example, one response type might include /// the information from the DNS header bytes while another might omit /// it. The amount and type of information that a media type gives is /// solely up to the format, and not defined in this protocol. /// /// The only response type defined in this document is "application/dns- /// message", but it is possible that other response formats will be /// defined in the future. /// /// The DNS response for "application/dns-message" in Section 7 MAY have /// one or more EDNS options [RFC6891], depending on the extension /// definition of the extensions given in the DNS request. /// /// Each DNS request-response pair is matched to one HTTP exchange. The /// responses may be processed and transported in any order using HTTP's /// multi-streaming functionality ([RFC7540] Section 5). /// /// Section 6.1 discusses the relationship between DNS and HTTP response /// caching. /// /// A DNS API server MUST be able to process application/dns-message /// request messages. /// /// A DNS API server SHOULD respond with HTTP status code 415 /// (Unsupported Media Type) upon receiving a media type it is unable to /// process. /// ``` fn send_message(&mut self, mut message: DnsRequest) -> DnsResponseStream { if self.is_shutdown { panic!("can not send messages after stream is shutdown") } // per the RFC, a zero id allows for the HTTP packet to be cached better message.set_id(0); let bytes = match message.to_vec() { Ok(bytes) => bytes, Err(err) => return err.into(), }; Box::pin(Self::inner_send( self.h2.clone(), Bytes::from(bytes), Arc::clone(&self.name_server_name), )) .into() } fn shutdown(&mut self) { self.is_shutdown = true; } fn is_shutdown(&self) -> bool { self.is_shutdown } } impl Stream for HttpsClientStream { type Item = Result<(), ProtoError>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.is_shutdown { return Poll::Ready(None); } // just checking if the connection is ok match self.h2.poll_ready(cx) { Poll::Ready(Ok(())) => Poll::Ready(Some(Ok(()))), Poll::Pending => Poll::Pending, Poll::Ready(Err(e)) => Poll::Ready(Some(Err(ProtoError::from(format!( "h2 stream errored: {e}", ))))), } } } /// A HTTPS connection builder for DNS-over-HTTPS #[derive(Clone)] pub struct HttpsClientStreamBuilder { client_config: Arc, bind_addr: Option, } impl HttpsClientStreamBuilder { /// Constructs a new TlsStreamBuilder with the associated ClientConfig pub fn with_client_config(client_config: Arc) -> Self { Self { client_config, bind_addr: None, } } /// Sets the address to connect from. pub fn bind_addr(&mut self, bind_addr: SocketAddr) { self.bind_addr = Some(bind_addr); } /// Creates a new HttpsStream to the specified name_server /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate pub fn build( mut self, name_server: SocketAddr, dns_name: String, ) -> HttpsClientConnect { // ensure the ALPN protocol is set correctly if self.client_config.alpn_protocols.is_empty() { let mut client_config = (*self.client_config).clone(); client_config.alpn_protocols = vec![ALPN_H2.to_vec()]; self.client_config = Arc::new(client_config); } let tls = TlsConfig { client_config: self.client_config, dns_name: Arc::from(dns_name), }; let connect = S::connect_with_bind(name_server, self.bind_addr); HttpsClientConnect::(HttpsClientConnectState::TcpConnecting { connect, name_server, tls: Some(tls), }) } /// Creates a new HttpsStream with existing connection pub fn build_with_future( future: F, mut client_config: Arc, name_server: SocketAddr, dns_name: String, ) -> HttpsClientConnect where S: DnsTcpStream, F: Future> + Send + Unpin + 'static, { // ensure the ALPN protocol is set correctly if client_config.alpn_protocols.is_empty() { let mut client_cfg = (*client_config).clone(); client_cfg.alpn_protocols = vec![ALPN_H2.to_vec()]; client_config = Arc::new(client_cfg); } let tls = TlsConfig { client_config, dns_name: Arc::from(dns_name), }; HttpsClientConnect::(HttpsClientConnectState::TcpConnecting { connect: Box::pin(future), name_server, tls: Some(tls), }) } } /// A future that resolves to an HttpsClientStream pub struct HttpsClientConnect(HttpsClientConnectState) where S: DnsTcpStream; impl Future for HttpsClientConnect where S: DnsTcpStream, { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.0.poll_unpin(cx) } } struct TlsConfig { client_config: Arc, dns_name: Arc, } #[allow(clippy::large_enum_variant)] #[allow(clippy::type_complexity)] enum HttpsClientConnectState where S: DnsTcpStream, { TcpConnecting { connect: Pin> + Send>>, name_server: SocketAddr, tls: Option, }, TlsConnecting { // TODO: also abstract away Tokio TLS in RuntimeProvider. tls: TokioTlsConnect>, name_server_name: Arc, name_server: SocketAddr, }, H2Handshake { handshake: Pin< Box< dyn Future< Output = Result< ( SendRequest, Connection>, Bytes>, ), h2::Error, >, > + Send, >, >, name_server_name: Arc, name_server: SocketAddr, }, Connected(Option), Errored(Option), } impl Future for HttpsClientConnectState where S: DnsTcpStream, { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { let next = match *self { Self::TcpConnecting { ref mut connect, name_server, ref mut tls, } => { let tcp = ready!(connect.poll_unpin(cx))?; debug!("tcp connection established to: {}", name_server); let tls = tls .take() .expect("programming error, tls should not be None here"); let name_server_name = Arc::clone(&tls.dns_name); match tls.dns_name.as_ref().try_into() { Ok(dns_name) => { let tls = TlsConnector::from(tls.client_config); let tls = tls.connect(dns_name, AsyncIoStdAsTokio(tcp)); Self::TlsConnecting { name_server_name, name_server, tls, } } Err(_) => Self::Errored(Some(ProtoError::from(format!( "bad dns_name: {}", &tls.dns_name )))), } } Self::TlsConnecting { ref name_server_name, name_server, ref mut tls, } => { let tls = ready!(tls.poll_unpin(cx))?; debug!("tls connection established to: {}", name_server); let mut handshake = h2::client::Builder::new(); handshake.enable_push(false); let handshake = handshake.handshake(tls); Self::H2Handshake { name_server_name: Arc::clone(name_server_name), name_server, handshake: Box::pin(handshake), } } Self::H2Handshake { ref name_server_name, name_server, ref mut handshake, } => { let (send_request, connection) = ready!(handshake .poll_unpin(cx) .map_err(|e| ProtoError::from(format!("h2 handshake error: {e}"))))?; // TODO: hand this back for others to run rather than spawning here? debug!("h2 connection established to: {}", name_server); tokio::spawn( connection .map_err(|e| warn!("h2 connection failed: {e}")) .map(|_: Result<(), ()>| ()), ); Self::Connected(Some(HttpsClientStream { name_server_name: Arc::clone(name_server_name), name_server, h2: send_request, is_shutdown: false, })) } Self::Connected(ref mut conn) => { return Poll::Ready(Ok(conn.take().expect("cannot poll after complete"))) } Self::Errored(ref mut err) => { return Poll::Ready(Err(err.take().expect("cannot poll after complete"))) } }; *self.as_mut().deref_mut() = next; } } } /// A future that resolves to pub struct HttpsClientResponse( Pin> + Send>>, ); impl Future for HttpsClientResponse { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.0.as_mut().poll(cx).map_err(ProtoError::from) } } #[cfg(any(feature = "webpki-roots", feature = "native-certs"))] #[cfg(test)] mod tests { use std::net::SocketAddr; use std::str::FromStr; use rustls::KeyLogFile; use tokio::net::TcpStream as TokioTcpStream; use tokio::runtime::Runtime; use crate::iocompat::AsyncIoTokioAsStd; use crate::op::{Message, Query, ResponseCode}; use crate::rr::rdata::{A, AAAA}; use crate::rr::{Name, RData, RecordType}; use crate::xfer::{DnsRequestOptions, FirstAnswer}; use super::*; #[test] fn test_https_google() { //env_logger::try_init().ok(); let google = SocketAddr::from(([8, 8, 8, 8], 443)); let mut request = Message::new(); let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); let mut client_config = client_config_tls12(); client_config.key_log = Arc::new(KeyLogFile::new()); let https_builder = HttpsClientStreamBuilder::with_client_config(Arc::new(client_config)); let connect = https_builder .build::>(google, "dns.google".to_string()); // tokio runtime stuff... let runtime = Runtime::new().expect("could not start runtime"); let mut https = runtime.block_on(connect).expect("https connect failed"); let response = runtime .block_on(https.send_message(request).first_answer()) .expect("send_message failed"); let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_a) .expect("Expected A record"); assert_eq!(addr, &A::new(93, 184, 216, 34)); // // assert that the connection works for a second query let mut request = Message::new(); let query = Query::query( Name::from_str("www.example.com.").unwrap(), RecordType::AAAA, ); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); for _ in 0..3 { let response = runtime .block_on(https.send_message(request.clone()).first_answer()) .expect("send_message failed"); if response.response_code() == ResponseCode::ServFail { continue; } let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_aaaa) .expect("invalid response, expected A record"); assert_eq!( addr, &AAAA::new(0x2606, 0x2800, 0x0220, 0x0001, 0x0248, 0x1893, 0x25c8, 0x1946) ); } } #[test] fn test_https_google_with_pure_ip_address_server() { //env_logger::try_init().ok(); let google = SocketAddr::from(([8, 8, 8, 8], 443)); let mut request = Message::new(); let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); let mut client_config = client_config_tls12(); client_config.key_log = Arc::new(KeyLogFile::new()); let https_builder = HttpsClientStreamBuilder::with_client_config(Arc::new(client_config)); let connect = https_builder .build::>(google, google.ip().to_string()); // tokio runtime stuff... let runtime = Runtime::new().expect("could not start runtime"); let mut https = runtime.block_on(connect).expect("https connect failed"); let response = runtime .block_on(https.send_message(request).first_answer()) .expect("send_message failed"); let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_a) .expect("Expected A record"); assert_eq!(addr, &A::new(93, 184, 216, 34)); // // assert that the connection works for a second query let mut request = Message::new(); let query = Query::query( Name::from_str("www.example.com.").unwrap(), RecordType::AAAA, ); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); for _ in 0..3 { let response = runtime .block_on(https.send_message(request.clone()).first_answer()) .expect("send_message failed"); if response.response_code() == ResponseCode::ServFail { continue; } let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_aaaa) .expect("invalid response, expected A record"); assert_eq!( addr, &AAAA::new(0x2606, 0x2800, 0x0220, 0x0001, 0x0248, 0x1893, 0x25c8, 0x1946) ); } } #[test] #[ignore] // cloudflare has been unreliable as a public test service. fn test_https_cloudflare() { // self::env_logger::try_init().ok(); let cloudflare = SocketAddr::from(([1, 1, 1, 1], 443)); let mut request = Message::new(); let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); let client_config = client_config_tls12(); let https_builder = HttpsClientStreamBuilder::with_client_config(Arc::new(client_config)); let connect = https_builder.build::>( cloudflare, "cloudflare-dns.com".to_string(), ); // tokio runtime stuff... let runtime = Runtime::new().expect("could not start runtime"); let mut https = runtime.block_on(connect).expect("https connect failed"); let response = runtime .block_on(https.send_message(request).first_answer()) .expect("send_message failed"); let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_a) .expect("invalid response, expected A record"); assert_eq!(addr, &A::new(93, 184, 216, 34)); // // assert that the connection works for a second query let mut request = Message::new(); let query = Query::query( Name::from_str("www.example.com.").unwrap(), RecordType::AAAA, ); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); let response = runtime .block_on(https.send_message(request).first_answer()) .expect("send_message failed"); let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_aaaa) .expect("invalid response, expected A record"); assert_eq!( addr, &AAAA::new(0x2606, 0x2800, 0x0220, 0x0001, 0x0248, 0x1893, 0x25c8, 0x1946) ); } fn client_config_tls12() -> ClientConfig { use rustls::RootCertStore; #[cfg_attr( not(any(feature = "native-certs", feature = "webpki-roots")), allow(unused_mut) )] let mut root_store = RootCertStore::empty(); #[cfg(all(feature = "native-certs", not(feature = "webpki-roots")))] { let (added, ignored) = root_store .add_parsable_certificates(&rustls_native_certs::load_native_certs().unwrap()); if ignored > 0 { warn!( "failed to parse {} certificate(s) from the native root store", ignored ); } if added == 0 { panic!("no valid certificates found in the native root store"); } } #[cfg(feature = "webpki-roots")] root_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( ta.subject, ta.spki, ta.name_constraints, ) })); let mut client_config = ClientConfig::builder() .with_safe_default_cipher_suites() .with_safe_default_kx_groups() .with_safe_default_protocol_versions() .unwrap() .with_root_certificates(root_store) .with_no_client_auth(); client_config.alpn_protocols = vec![ALPN_H2.to_vec()]; client_config } } hickory-proto-0.24.0/src/h2/h2_server.rs000064400000000000000000000107341046102023000160720ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! HTTPS related server items use std::fmt::Debug; use std::str::FromStr; use std::sync::Arc; use bytes::{Bytes, BytesMut}; use futures_util::stream::{Stream, StreamExt}; use h2; use http::header::CONTENT_LENGTH; use http::{Method, Request}; use tracing::debug; use crate::h2::HttpsError; use crate::http::Version; /// Given an HTTP request, return a future that will result in the next sequence of bytes. /// /// To allow downstream clients to do something interesting with the lifetime of the bytes, this doesn't /// perform a conversion to a Message, only collects all the bytes. pub async fn message_from( this_server_name: Option>, request: Request, ) -> Result where R: Stream> + 'static + Send + Debug + Unpin, { debug!("Received request: {:#?}", request); let this_server_name = this_server_name.as_deref(); match crate::http::request::verify(Version::Http2, this_server_name, &request) { Ok(_) => (), Err(err) => return Err(err), } // attempt to get the content length let mut content_length = None; if let Some(length) = request.headers().get(CONTENT_LENGTH) { let length = usize::from_str(length.to_str()?)?; debug!("got message length: {}", length); content_length = Some(length); } match *request.method() { Method::GET => Err(format!("GET unimplemented: {}", request.method()).into()), Method::POST => message_from_post(request.into_body(), content_length).await, _ => Err(format!("bad method: {}", request.method()).into()), } } /// Deserialize the message from a POST message pub(crate) async fn message_from_post( mut request_stream: R, length: Option, ) -> Result where R: Stream> + 'static + Send + Debug + Unpin, { let mut bytes = BytesMut::with_capacity(length.unwrap_or(0).clamp(512, 4096)); loop { match request_stream.next().await { Some(Ok(mut frame)) => bytes.extend_from_slice(&frame.split_off(0)), Some(Err(err)) => return Err(err.into()), None => { return if let Some(length) = length { // wait until we have all the bytes if bytes.len() == length { Ok(bytes) } else { Err("not all bytes received".into()) } } else { Ok(bytes) }; } }; if let Some(length) = length { // wait until we have all the bytes if bytes.len() == length { return Ok(bytes); } } } } #[cfg(test)] mod tests { use futures_executor::block_on; use std::pin::Pin; use std::task::{Context, Poll}; use crate::http::request; use crate::op::Message; use super::*; #[derive(Debug)] struct TestBytesStream(Vec>); impl Stream for TestBytesStream { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { match self.0.pop() { Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes))), Some(Err(err)) => Poll::Ready(Some(Err(err))), None => Poll::Ready(None), } } } #[test] fn test_from_post() { let message = Message::new(); let msg_bytes = message.to_vec().unwrap(); let len = msg_bytes.len(); let stream = TestBytesStream(vec![Ok(Bytes::from(msg_bytes))]); let request = request::new(Version::Http2, "ns.example.com", len).unwrap(); let request = request.map(|()| stream); let from_post = message_from(Some(Arc::from("ns.example.com")), request); let bytes = match block_on(from_post) { Ok(bytes) => bytes, e => panic!("{:#?}", e), }; let msg_from_post = Message::from_vec(bytes.as_ref()).expect("bytes failed"); assert_eq!(message, msg_from_post); } } hickory-proto-0.24.0/src/h2/mod.rs000064400000000000000000000012271046102023000147470ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! TLS protocol related components for DNS over HTTPS (DoH) mod h2_client_stream; pub mod h2_server; pub use crate::http::error::{Error as HttpsError, Result as HttpsResult}; pub use self::h2_client_stream::{ HttpsClientConnect, HttpsClientResponse, HttpsClientStream, HttpsClientStreamBuilder, }; hickory-proto-0.24.0/src/h3/h3_client_stream.rs000064400000000000000000000552271046102023000174250ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::fmt::{self, Display}; use std::future::Future; use std::net::SocketAddr; use std::pin::Pin; use std::str::FromStr; use std::sync::Arc; use std::task::{Context, Poll}; use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures_util::future::FutureExt; use futures_util::stream::Stream; use h3::client::{Connection, SendRequest}; use h3_quinn::OpenStreams; use http::header::{self, CONTENT_LENGTH}; use quinn::{ClientConfig, Endpoint, EndpointConfig, TransportConfig}; use rustls::ClientConfig as TlsClientConfig; use tracing::debug; use crate::error::ProtoError; use crate::http::Version; use crate::op::Message; use crate::quic::quic_socket::QuinnAsyncUdpSocketAdapter; use crate::quic::QuicLocalAddr; use crate::udp::{DnsUdpSocket, UdpSocket}; use crate::xfer::{DnsRequest, DnsRequestSender, DnsResponse, DnsResponseStream}; use super::ALPN_H3; /// A DNS client connection for DNS-over-HTTP/3 #[must_use = "futures do nothing unless polled"] pub struct H3ClientStream { // Corresponds to the dns-name of the HTTP/3 server name_server_name: Arc, name_server: SocketAddr, driver: Connection, send_request: SendRequest, is_shutdown: bool, } impl Display for H3ClientStream { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( formatter, "H3({},{})", self.name_server, self.name_server_name ) } } impl H3ClientStream { /// Builder for H3ClientStream pub fn builder() -> H3ClientStreamBuilder { H3ClientStreamBuilder::default() } async fn inner_send( mut h3: SendRequest, message: Bytes, name_server_name: Arc, ) -> Result { // build up the http request let request = crate::http::request::new(Version::Http3, &name_server_name, message.remaining()); let request = request.map_err(|err| ProtoError::from(format!("bad http request: {err}")))?; debug!("request: {:#?}", request); // Send the request let mut stream = h3 .send_request(request) .await .map_err(|err| ProtoError::from(format!("h3 send_request error: {err}")))?; stream .send_data(message) .await .map_err(|e| ProtoError::from(format!("h3 send_data error: {e}")))?; stream .finish() .await .map_err(|err| ProtoError::from(format!("received a stream error: {err}")))?; let response = stream .recv_response() .await .map_err(|err| ProtoError::from(format!("h3 recv_response error: {err}")))?; debug!("got response: {:#?}", response); // get the length of packet let content_length = response .headers() .get(CONTENT_LENGTH) .map(|v| v.to_str()) .transpose() .map_err(|e| ProtoError::from(format!("bad headers received: {e}")))? .map(usize::from_str) .transpose() .map_err(|e| ProtoError::from(format!("bad headers received: {e}")))?; // TODO: what is a good max here? // clamp(512, 4096) says make sure it is at least 512 bytes, and min 4096 says it is at most 4k // just a little protection from malicious actors. let mut response_bytes = BytesMut::with_capacity(content_length.unwrap_or(512).clamp(512, 4096)); while let Some(partial_bytes) = stream .recv_data() .await .map_err(|e| ProtoError::from(format!("h3 recv_data error: {e}")))? { debug!("got bytes: {}", partial_bytes.remaining()); response_bytes.put(partial_bytes); // assert the length if let Some(content_length) = content_length { if response_bytes.len() >= content_length { break; } } } // assert the length if let Some(content_length) = content_length { if response_bytes.len() != content_length { // TODO: make explicit error type return Err(ProtoError::from(format!( "expected byte length: {}, got: {}", content_length, response_bytes.len() ))); } } // Was it a successful request? if !response.status().is_success() { let error_string = String::from_utf8_lossy(response_bytes.as_ref()); // TODO: make explicit error type return Err(ProtoError::from(format!( "http unsuccessful code: {}, message: {}", response.status(), error_string ))); } else { // verify content type { // in the case that the ContentType is not specified, we assume it's the standard DNS format let content_type = response .headers() .get(header::CONTENT_TYPE) .map(|h| { h.to_str().map_err(|err| { // TODO: make explicit error type ProtoError::from(format!("ContentType header not a string: {err}")) }) }) .unwrap_or(Ok(crate::http::MIME_APPLICATION_DNS))?; if content_type != crate::http::MIME_APPLICATION_DNS { return Err(ProtoError::from(format!( "ContentType unsupported (must be '{}'): '{}'", crate::http::MIME_APPLICATION_DNS, content_type ))); } } }; // and finally convert the bytes into a DNS message let message = Message::from_vec(&response_bytes)?; Ok(DnsResponse::new(message, response_bytes.to_vec())) } } impl DnsRequestSender for H3ClientStream { /// This indicates that the HTTP message was successfully sent, and we now have the response.RecvStream /// /// If the request fails, this will return the error, and it should be assumed that the Stream portion of /// this will have no date. /// /// ```text /// 5.2. The HTTP Response /// /// An HTTP response with a 2xx status code ([RFC7231] Section 6.3) /// indicates a valid DNS response to the query made in the HTTP request. /// A valid DNS response includes both success and failure responses. /// For example, a DNS failure response such as SERVFAIL or NXDOMAIN will /// be the message in a successful 2xx HTTP response even though there /// was a failure at the DNS layer. Responses with non-successful HTTP /// status codes do not contain DNS answers to the question in the /// corresponding request. Some of these non-successful HTTP responses /// (e.g., redirects or authentication failures) could mean that clients /// need to make new requests to satisfy the original question. /// /// Different response media types will provide more or less information /// from a DNS response. For example, one response type might include /// the information from the DNS header bytes while another might omit /// it. The amount and type of information that a media type gives is /// solely up to the format, and not defined in this protocol. /// /// The only response type defined in this document is "application/dns- /// message", but it is possible that other response formats will be /// defined in the future. /// /// The DNS response for "application/dns-message" in Section 7 MAY have /// one or more EDNS options [RFC6891], depending on the extension /// definition of the extensions given in the DNS request. /// /// Each DNS request-response pair is matched to one HTTP exchange. The /// responses may be processed and transported in any order using HTTP's /// multi-streaming functionality ([RFC7540] Section 5). /// /// Section 6.1 discusses the relationship between DNS and HTTP response /// caching. /// /// A DNS API server MUST be able to process application/dns-message /// request messages. /// /// A DNS API server SHOULD respond with HTTP status code 415 /// (Unsupported Media Type) upon receiving a media type it is unable to /// process. /// ``` fn send_message(&mut self, mut message: DnsRequest) -> DnsResponseStream { if self.is_shutdown { panic!("can not send messages after stream is shutdown") } // per the RFC, a zero id allows for the HTTP packet to be cached better message.set_id(0); let bytes = match message.to_vec() { Ok(bytes) => bytes, Err(err) => return err.into(), }; Box::pin(Self::inner_send( self.send_request.clone(), Bytes::from(bytes), Arc::clone(&self.name_server_name), )) .into() } fn shutdown(&mut self) { self.is_shutdown = true; } fn is_shutdown(&self) -> bool { self.is_shutdown } } impl Stream for H3ClientStream { type Item = Result<(), ProtoError>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.is_shutdown { return Poll::Ready(None); } // just checking if the connection is ok match self.driver.poll_close(cx) { Poll::Ready(Ok(())) => Poll::Ready(None), Poll::Pending => Poll::Pending, Poll::Ready(Err(e)) => Poll::Ready(Some(Err(ProtoError::from(format!( "h3 stream errored: {e}", ))))), } } } /// A H3 connection builder for DNS-over-HTTP/3 #[derive(Clone)] pub struct H3ClientStreamBuilder { crypto_config: TlsClientConfig, transport_config: Arc, bind_addr: Option, } impl H3ClientStreamBuilder { /// Constructs a new H3ClientStreamBuilder with the associated ClientConfig pub fn crypto_config(&mut self, crypto_config: TlsClientConfig) -> &mut Self { self.crypto_config = crypto_config; self } /// Sets the address to connect from. pub fn bind_addr(&mut self, bind_addr: SocketAddr) { self.bind_addr = Some(bind_addr); } /// Creates a new H3Stream to the specified name_server /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate pub fn build(self, name_server: SocketAddr, dns_name: String) -> H3ClientConnect { H3ClientConnect(Box::pin(self.connect(name_server, dns_name)) as _) } /// Creates a new H3Stream with existing connection pub fn build_with_future( self, future: F, name_server: SocketAddr, dns_name: String, ) -> H3ClientConnect where S: DnsUdpSocket + QuicLocalAddr + 'static, F: Future> + Send + Unpin + 'static, { H3ClientConnect(Box::pin(self.connect_with_future(future, name_server, dns_name)) as _) } async fn connect_with_future( self, future: F, name_server: SocketAddr, dns_name: String, ) -> Result where S: DnsUdpSocket + QuicLocalAddr + 'static, F: Future> + Send, { let socket = future.await?; let wrapper = QuinnAsyncUdpSocketAdapter { io: socket }; let endpoint = Endpoint::new_with_abstract_socket( EndpointConfig::default(), None, wrapper, Arc::new(quinn::TokioRuntime), )?; self.connect_inner(endpoint, name_server, dns_name).await } async fn connect( self, name_server: SocketAddr, dns_name: String, ) -> Result { let connect = if let Some(bind_addr) = self.bind_addr { ::connect_with_bind(name_server, bind_addr) } else { ::connect(name_server) }; let socket = connect.await?; let socket = socket.into_std()?; let endpoint = Endpoint::new( EndpointConfig::default(), None, socket, Arc::new(quinn::TokioRuntime), )?; self.connect_inner(endpoint, name_server, dns_name).await } async fn connect_inner( self, mut endpoint: Endpoint, name_server: SocketAddr, dns_name: String, ) -> Result { let mut crypto_config = self.crypto_config; // ensure the ALPN protocol is set correctly if crypto_config.alpn_protocols.is_empty() { crypto_config.alpn_protocols = vec![ALPN_H3.to_vec()]; } let early_data_enabled = crypto_config.enable_early_data; let mut client_config = ClientConfig::new(Arc::new(crypto_config)); client_config.transport_config(self.transport_config.clone()); endpoint.set_default_client_config(client_config); let connecting = endpoint.connect(name_server, &dns_name)?; // TODO: for Client/Dynamic update, don't use RTT, for queries, do use it. let quic_connection = if early_data_enabled { match connecting.into_0rtt() { Ok((new_connection, _)) => new_connection, Err(connecting) => connecting.await?, } } else { connecting.await? }; let h3_connection = h3_quinn::Connection::new(quic_connection); let (driver, send_request) = h3::client::new(h3_connection) .await .map_err(|e| ProtoError::from(format!("h3 connection failed: {e}")))?; Ok(H3ClientStream { name_server_name: Arc::from(dns_name), name_server, driver, send_request, is_shutdown: false, }) } } impl Default for H3ClientStreamBuilder { fn default() -> Self { Self { crypto_config: super::client_config_tls13().unwrap(), transport_config: Arc::new(super::transport()), bind_addr: None, } } } /// A future that resolves to an H3ClientStream pub struct H3ClientConnect( Pin> + Send>>, ); impl Future for H3ClientConnect { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.0.poll_unpin(cx) } } /// A future that resolves to pub struct H3ClientResponse(Pin> + Send>>); impl Future for H3ClientResponse { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.0.as_mut().poll(cx).map_err(ProtoError::from) } } #[cfg(all(test, any(feature = "native-certs", feature = "webpki-roots")))] mod tests { use std::net::SocketAddr; use std::str::FromStr; use rustls::KeyLogFile; use tokio::runtime::Runtime; use crate::op::{Message, Query, ResponseCode}; use crate::rr::rdata::{A, AAAA}; use crate::rr::{Name, RData, RecordType}; use crate::xfer::{DnsRequestOptions, FirstAnswer}; use super::*; #[test] fn test_h3_google() { //env_logger::try_init().ok(); let google = SocketAddr::from(([8, 8, 8, 8], 443)); let mut request = Message::new(); let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); let mut client_config = super::super::client_config_tls13().unwrap(); client_config.key_log = Arc::new(KeyLogFile::new()); let mut h3_builder = H3ClientStream::builder(); h3_builder.crypto_config(client_config); let connect = h3_builder.build(google, "dns.google".to_string()); // tokio runtime stuff... let runtime = Runtime::new().expect("could not start runtime"); let mut h3 = runtime.block_on(connect).expect("h3 connect failed"); let response = runtime .block_on(h3.send_message(request).first_answer()) .expect("send_message failed"); let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_a) .expect("Expected A record"); assert_eq!(addr, &A::new(93, 184, 216, 34)); // // assert that the connection works for a second query let mut request = Message::new(); let query = Query::query( Name::from_str("www.example.com.").unwrap(), RecordType::AAAA, ); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); for _ in 0..3 { let response = runtime .block_on(h3.send_message(request.clone()).first_answer()) .expect("send_message failed"); if response.response_code() == ResponseCode::ServFail { continue; } let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_aaaa) .expect("invalid response, expected A record"); assert_eq!( addr, &AAAA::new(0x2606, 0x2800, 0x0220, 0x0001, 0x0248, 0x1893, 0x25c8, 0x1946) ); } } #[test] fn test_h3_google_with_pure_ip_address_server() { //env_logger::try_init().ok(); let google = SocketAddr::from(([8, 8, 8, 8], 443)); let mut request = Message::new(); let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); let mut client_config = super::super::client_config_tls13().unwrap(); client_config.key_log = Arc::new(KeyLogFile::new()); let mut h3_builder = H3ClientStream::builder(); h3_builder.crypto_config(client_config); let connect = h3_builder.build(google, google.ip().to_string()); // tokio runtime stuff... let runtime = Runtime::new().expect("could not start runtime"); let mut h3 = runtime.block_on(connect).expect("h3 connect failed"); let response = runtime .block_on(h3.send_message(request).first_answer()) .expect("send_message failed"); let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_a) .expect("Expected A record"); assert_eq!(addr, &A::new(93, 184, 216, 34)); // // assert that the connection works for a second query let mut request = Message::new(); let query = Query::query( Name::from_str("www.example.com.").unwrap(), RecordType::AAAA, ); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); for _ in 0..3 { let response = runtime .block_on(h3.send_message(request.clone()).first_answer()) .expect("send_message failed"); if response.response_code() == ResponseCode::ServFail { continue; } let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_aaaa) .expect("invalid response, expected A record"); assert_eq!( addr, &AAAA::new(0x2606, 0x2800, 0x0220, 0x0001, 0x0248, 0x1893, 0x25c8, 0x1946) ); } } /// Currently fails, see . #[test] #[ignore] // cloudflare has been unreliable as a public test service. fn test_h3_cloudflare() { // self::env_logger::try_init().ok(); let cloudflare = SocketAddr::from(([1, 1, 1, 1], 443)); let mut request = Message::new(); let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); let mut client_config = super::super::client_config_tls13().unwrap(); client_config.key_log = Arc::new(KeyLogFile::new()); let mut h3_builder = H3ClientStream::builder(); h3_builder.crypto_config(client_config); let connect = h3_builder.build(cloudflare, "cloudflare-dns.com".to_string()); // tokio runtime stuff... let runtime = Runtime::new().expect("could not start runtime"); let mut h3 = runtime.block_on(connect).expect("h3 connect failed"); let response = runtime .block_on(h3.send_message(request).first_answer()) .expect("send_message failed"); let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_a) .expect("invalid response, expected A record"); assert_eq!(addr, &A::new(93, 184, 216, 34)); // // assert that the connection works for a second query let mut request = Message::new(); let query = Query::query( Name::from_str("www.example.com.").unwrap(), RecordType::AAAA, ); request.add_query(query); let request = DnsRequest::new(request, DnsRequestOptions::default()); let response = runtime .block_on(h3.send_message(request).first_answer()) .expect("send_message failed"); let record = &response.answers()[0]; let addr = record .data() .and_then(RData::as_aaaa) .expect("invalid response, expected A record"); assert_eq!( addr, &AAAA::new(0x2606, 0x2800, 0x0220, 0x0001, 0x0248, 0x1893, 0x25c8, 0x1946) ); } } hickory-proto-0.24.0/src/h3/h3_server.rs000064400000000000000000000103361046102023000160720ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! HTTP/3 related server items use std::{io, net::SocketAddr, sync::Arc}; use bytes::Bytes; use h3::server::{Connection, RequestStream}; use h3_quinn::{BidiStream, Endpoint}; use http::Request; use quinn::{EndpointConfig, ServerConfig}; use rustls::{server::ServerConfig as TlsServerConfig, version::TLS13, Certificate, PrivateKey}; use crate::{error::ProtoError, udp::UdpSocket}; use super::ALPN_H3; /// A DNS-over-HTTP/3 Server, see H3ClientStream for the client counterpart pub struct H3Server { endpoint: Endpoint, } impl H3Server { /// Construct the new Acceptor with the associated pkcs12 data pub async fn new( name_server: SocketAddr, cert: Vec, key: PrivateKey, ) -> Result { // setup a new socket for the server to use let socket = ::bind(name_server).await?; Self::with_socket(socket, cert, key) } /// Construct the new server with an existing socket pub fn with_socket( socket: tokio::net::UdpSocket, cert: Vec, key: PrivateKey, ) -> Result { let mut config = TlsServerConfig::builder() .with_safe_default_cipher_suites() .with_safe_default_kx_groups() .with_protocol_versions(&[&TLS13]) .expect("TLS1.3 not supported") .with_no_client_auth() .with_single_cert(cert, key)?; config.alpn_protocols = vec![ALPN_H3.to_vec()]; let mut server_config = ServerConfig::with_crypto(Arc::new(config)); server_config.transport = Arc::new(super::transport()); let socket = socket.into_std()?; let endpoint = Endpoint::new( EndpointConfig::default(), Some(server_config), socket, Arc::new(quinn::TokioRuntime), )?; Ok(Self { endpoint }) } /// Accept the next incoming connection. /// /// # Returns /// /// A remote connection that could accept many potential requests and the remote socket address pub async fn accept(&mut self) -> Result, ProtoError> { let connecting = match self.endpoint.accept().await { Some(conn) => conn, None => return Ok(None), }; let remote_addr = connecting.remote_address(); let connection = connecting.await?; Ok(Some(( H3Connection { connection: Connection::new(h3_quinn::Connection::new(connection)) .await .map_err(|e| ProtoError::from(format!("h3 connection failed: {e}")))?, }, remote_addr, ))) } /// Returns the address this server is listening on /// /// This can be useful in tests, where a random port can be associated with the server by binding on `127.0.0.1:0` and then getting the /// associated port address with this function. pub fn local_addr(&self) -> Result { self.endpoint.local_addr() } } /// A HTTP/3 connection. pub struct H3Connection { connection: Connection, } impl H3Connection { /// Accept the next request from the client pub async fn accept( &mut self, ) -> Option, RequestStream, Bytes>), ProtoError>> { match self.connection.accept().await { Ok(Some((request, stream))) => Some(Ok((request, stream))), Ok(None) => None, Err(e) => Some(Err(ProtoError::from(format!("h3 request failed: {e}")))), } } /// Shutdown the connection. pub async fn shutdown(&mut self) -> Result<(), ProtoError> { self.connection .shutdown(0) .await .map_err(|e| ProtoError::from(format!("h3 connection shutdown failed: {e}"))) } } hickory-proto-0.24.0/src/h3/mod.rs000064400000000000000000000024711046102023000147520ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! TLS protocol related components for DNS over HTTP/3 (DoH3) mod h3_client_stream; pub mod h3_server; use quinn::{TransportConfig, VarInt}; pub use crate::http::error::{Error as H3Error, Result as H3Result}; pub use crate::quic::client_config_tls13; pub use self::h3_client_stream::{ H3ClientConnect, H3ClientResponse, H3ClientStream, H3ClientStreamBuilder, }; const ALPN_H3: &[u8] = b"h3"; /// Returns a default endpoint configuration for DNS-over-QUIC fn transport() -> TransportConfig { let mut transport_config = TransportConfig::default(); transport_config.datagram_receive_buffer_size(None); transport_config.datagram_send_buffer_size(0); // clients never accept new bidirectional streams transport_config.max_concurrent_bidi_streams(VarInt::from_u32(3)); // - SETTINGS // - QPACK encoder // - QPACK decoder // - RESERVED (GREASE) transport_config.max_concurrent_uni_streams(VarInt::from_u32(4)); transport_config } hickory-proto-0.24.0/src/http/error.rs000064400000000000000000000067611046102023000157770ustar 00000000000000// Copyright 2015-2020 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::num::ParseIntError; use std::{fmt, io}; use crate::error::ProtoError; use http::header::ToStrError; use thiserror::Error; #[cfg(feature = "backtrace")] use crate::{trace, ExtBacktrace}; /// An alias for results returned by functions of this crate pub type Result = ::std::result::Result; // TODO: remove this and put in ProtoError #[derive(Debug, Error)] #[non_exhaustive] pub enum ErrorKind { /// Unable to decode header value to string #[error("header decode error: {0}")] Decode(#[from] ToStrError), /// An error with an arbitrary message, referenced as &'static str #[error("{0}")] Message(&'static str), /// An error with an arbitrary message, stored as String #[error("{0}")] Msg(String), /// Unable to parse header value as number #[error("unable to parse number: {0}")] ParseInt(#[from] ParseIntError), #[error("proto error: {0}")] ProtoError(#[from] ProtoError), #[error("h2: {0}")] #[cfg(feature = "dns-over-https")] H2(#[from] h2::Error), #[error("h3: {0}")] #[cfg(feature = "dns-over-h3")] H3(#[from] h3::Error), } /// The error type for errors that get returned in the crate #[derive(Debug)] pub struct Error { kind: ErrorKind, #[cfg(feature = "backtrace")] backtrack: Option, } impl Error { /// Get the kind of the error pub fn kind(&self) -> &ErrorKind { &self.kind } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { cfg_if::cfg_if! { if #[cfg(feature = "backtrace")] { if let Some(ref backtrace) = self.backtrack { fmt::Display::fmt(&self.kind, f)?; fmt::Debug::fmt(backtrace, f) } else { fmt::Display::fmt(&self.kind, f) } } else { fmt::Display::fmt(&self.kind, f) } } } } impl From for Error { fn from(kind: ErrorKind) -> Self { Self { kind, #[cfg(feature = "backtrace")] backtrack: trace!(), } } } impl From<&'static str> for Error { fn from(msg: &'static str) -> Self { ErrorKind::Message(msg).into() } } impl From for Error { fn from(msg: String) -> Self { ErrorKind::Msg(msg).into() } } impl From for Error { fn from(err: ParseIntError) -> Self { ErrorKind::from(err).into() } } impl From for Error { fn from(err: ToStrError) -> Self { ErrorKind::from(err).into() } } impl From for Error { fn from(msg: ProtoError) -> Self { ErrorKind::ProtoError(msg).into() } } #[cfg(feature = "dns-over-https")] impl From for Error { fn from(msg: h2::Error) -> Self { ErrorKind::H2(msg).into() } } #[cfg(feature = "dns-over-h3")] impl From for Error { fn from(msg: h3::Error) -> Self { ErrorKind::H3(msg).into() } } impl From for io::Error { fn from(err: Error) -> Self { Self::new(io::ErrorKind::Other, format!("https: {err}")) } } hickory-proto-0.24.0/src/http/mod.rs000064400000000000000000000021771046102023000154220ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! HTTP protocol related components for DNS over HTTP/2 (DoH) and HTTP/3 (DoH3) pub(crate) const MIME_APPLICATION_DNS: &str = "application/dns-message"; pub(crate) const DNS_QUERY_PATH: &str = "/dns-query"; pub(crate) mod error; pub mod request; pub mod response; /// Represents a version of the HTTP spec. #[derive(Clone, Copy, Debug)] pub enum Version { /// HTTP/2 for DoH. #[cfg(feature = "dns-over-https")] Http2, /// HTTP/3 for DoH3. #[cfg(feature = "dns-over-h3")] Http3, } impl Version { fn to_http(self) -> http::Version { match self { #[cfg(feature = "dns-over-https")] Self::Http2 => http::Version::HTTP_2, #[cfg(feature = "dns-over-h3")] Self::Http3 => http::Version::HTTP_3, } } } hickory-proto-0.24.0/src/http/request.rs000064400000000000000000000140301046102023000163220ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! HTTP request creation and validation use std::str::FromStr; use http::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE}; use http::{header, uri, Request, Uri}; use tracing::debug; use crate::error::ProtoError; use crate::http::error::Result; use crate::http::Version; /// Create a new Request for an http dns-message request /// /// ```text /// https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-10#section-5.1 /// The URI Template defined in this document is processed without any /// variables when the HTTP method is POST. When the HTTP method is GET /// the single variable "dns" is defined as the content of the DNS /// request (as described in Section 7), encoded with base64url /// [RFC4648]. /// ``` #[allow(clippy::field_reassign_with_default)] // https://github.com/rust-lang/rust-clippy/issues/6527 pub fn new(version: Version, name_server_name: &str, message_len: usize) -> Result> { // TODO: this is basically the GET version, but it is more expensive than POST // perhaps add an option if people want better HTTP caching options. // let query = BASE64URL_NOPAD.encode(&message); // let url = format!("/dns-query?dns={}", query); // let request = Request::get(&url) // .header(header::CONTENT_TYPE, ::MIME_DNS_BINARY) // .header(header::HOST, &self.name_server_name as &str) // .header("authority", &self.name_server_name as &str) // .header(header::USER_AGENT, USER_AGENT) // .body(()); let mut parts = uri::Parts::default(); parts.path_and_query = Some(uri::PathAndQuery::from_static(crate::http::DNS_QUERY_PATH)); parts.scheme = Some(uri::Scheme::HTTPS); parts.authority = Some( uri::Authority::from_str(name_server_name) .map_err(|e| ProtoError::from(format!("invalid authority: {e}")))?, ); let url = Uri::from_parts(parts).map_err(|e| ProtoError::from(format!("uri parse error: {e}")))?; // TODO: add user agent to TypedHeaders let request = Request::builder() .method("POST") .uri(url) .version(version.to_http()) .header(CONTENT_TYPE, crate::http::MIME_APPLICATION_DNS) .header(ACCEPT, crate::http::MIME_APPLICATION_DNS) .header(CONTENT_LENGTH, message_len) .body(()) .map_err(|e| ProtoError::from(format!("http stream errored: {e}")))?; Ok(request) } /// Verifies the request is something we know what to deal with pub fn verify(version: Version, name_server: Option<&str>, request: &Request) -> Result<()> { // Verify all HTTP parameters let uri = request.uri(); // validate path if uri.path() != crate::http::DNS_QUERY_PATH { return Err(format!( "bad path: {}, expected: {}", uri.path(), crate::http::DNS_QUERY_PATH ) .into()); } // we only accept HTTPS if Some(&uri::Scheme::HTTPS) != uri.scheme() { return Err("must be HTTPS scheme".into()); } // the authority must match our nameserver name if let Some(name_server) = name_server { if let Some(authority) = uri.authority() { if authority.host() != name_server { return Err("incorrect authority".into()); } } else { return Err("no authority in HTTPS request".into()); } } // TODO: switch to mime::APPLICATION_DNS when that stabilizes match request.headers().get(CONTENT_TYPE).map(|v| v.to_str()) { Some(Ok(ctype)) if ctype == crate::http::MIME_APPLICATION_DNS => {} _ => return Err("unsupported content type".into()), }; // TODO: switch to mime::APPLICATION_DNS when that stabilizes match request.headers().get(ACCEPT).map(|v| v.to_str()) { Some(Ok(ctype)) => { let mut found = false; for mime_and_quality in ctype.split(',') { let mut parts = mime_and_quality.splitn(2, ';'); match parts.next() { Some(mime) if mime.trim() == crate::http::MIME_APPLICATION_DNS => { found = true; break; } Some(mime) if mime.trim() == "application/*" => { found = true; break; } _ => continue, } } if !found { return Err("does not accept content type".into()); } } Some(Err(e)) => return Err(e.into()), None => return Err("Accept is unspecified".into()), }; if request.version() != version.to_http() { let message = match version { #[cfg(feature = "dns-over-https")] Version::Http2 => "only HTTP/2 supported", #[cfg(feature = "dns-over-h3")] Version::Http3 => "only HTTP/3 supported", }; return Err(message.into()); } debug!( "verified request from: {}", request .headers() .get(header::USER_AGENT) .map(|h| h.to_str().unwrap_or("bad user agent")) .unwrap_or("unknown user agent") ); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] #[cfg(feature = "dns-over-https")] fn test_new_verify_h2() { let request = new(Version::Http2, "ns.example.com", 512).expect("error converting to http"); assert!(verify(Version::Http2, Some("ns.example.com"), &request).is_ok()); } #[test] #[cfg(feature = "dns-over-h3")] fn test_new_verify_h3() { let request = new(Version::Http3, "ns.example.com", 512).expect("error converting to http"); assert!(verify(Version::Http3, Some("ns.example.com"), &request).is_ok()); } } hickory-proto-0.24.0/src/http/response.rs000064400000000000000000000042761046102023000165030ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! HTTP request creation and validation use http::header::{CONTENT_LENGTH, CONTENT_TYPE}; use http::{Response, StatusCode}; use crate::error::ProtoError; use crate::http::error::Result; use crate::http::Version; /// Create a new Response for an http dns-message request /// /// ```text /// 4.2.1. Handling DNS and HTTP Errors /// /// DNS response codes indicate either success or failure for the DNS /// query. A successful HTTP response with a 2xx status code ([RFC7231] /// Section 6.3) is used for any valid DNS response, regardless of the /// DNS response code. For example, a successful 2xx HTTP status code is /// used even with a DNS message whose DNS response code indicates /// failure, such as SERVFAIL or NXDOMAIN. /// /// HTTP responses with non-successful HTTP status codes do not contain /// replies to the original DNS question in the HTTP request. DoH /// /// clients need to use the same semantic processing of non-successful /// HTTP status codes as other HTTP clients. This might mean that the /// DoH client retries the query with the same DoH server, such as if /// there are authorization failures (HTTP status code 401 [RFC7235] /// Section 3.1). It could also mean that the DoH client retries with a /// different DoH server, such as for unsupported media types (HTTP /// status code 415, [RFC7231] Section 6.5.13), or where the server /// cannot generate a representation suitable for the client (HTTP status /// code 406, [RFC7231] Section 6.5.6), and so on. /// ``` pub fn new(version: Version, message_len: usize) -> Result> { Response::builder() .status(StatusCode::OK) .version(version.to_http()) .header(CONTENT_TYPE, crate::http::MIME_APPLICATION_DNS) .header(CONTENT_LENGTH, message_len) .body(()) .map_err(|e| ProtoError::from(format!("invalid response: {e}")).into()) } hickory-proto-0.24.0/src/lib.rs000064400000000000000000000210211046102023000144170ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // Copyright 2017 Google LLC. // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. // LIBRARY WARNINGS #![warn( clippy::default_trait_access, clippy::dbg_macro, clippy::print_stdout, clippy::unimplemented, clippy::use_self, missing_copy_implementations, missing_docs, non_snake_case, non_upper_case_globals, rust_2018_idioms, unreachable_pub )] #![allow( clippy::single_component_path_imports, clippy::upper_case_acronyms, // can be removed on a major release boundary clippy::bool_to_int_with_if, )] #![recursion_limit = "2048"] #![cfg_attr(docsrs, feature(doc_cfg))] //! Hickory DNS Protocol library use async_trait::async_trait; use futures_util::future::Future; use std::marker::Send; use std::time::Duration; #[cfg(any(test, feature = "tokio-runtime"))] use tokio::runtime::Runtime; #[cfg(any(test, feature = "tokio-runtime"))] use tokio::task::JoinHandle; macro_rules! try_ready_stream { ($e:expr) => {{ match $e { Poll::Ready(Some(Ok(t))) => t, Poll::Ready(None) => return Poll::Ready(None), Poll::Pending => return Poll::Pending, Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(From::from(e)))), } }}; } /// Spawn a background task, if it was present #[cfg(any(test, feature = "tokio-runtime"))] #[cfg_attr(docsrs, doc(cfg(feature = "tokio-runtime")))] pub fn spawn_bg + Send + 'static, R: Send + 'static>( runtime: &Runtime, background: F, ) -> JoinHandle { runtime.spawn(background) } pub mod error; #[cfg(feature = "dns-over-https")] #[cfg_attr(docsrs, doc(cfg(feature = "dns-over-https")))] pub mod h2; #[cfg(feature = "dns-over-h3")] #[cfg_attr(docsrs, doc(cfg(feature = "dns-over-h3")))] pub mod h3; #[cfg(any(feature = "dns-over-https", feature = "dns-over-h3"))] #[cfg_attr( docsrs, doc(cfg(any(feature = "dns-over-https", feature = "dns-over-h3"))) )] pub mod http; #[cfg(feature = "mdns")] #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))] pub mod multicast; #[cfg(feature = "dns-over-native-tls")] #[cfg_attr(docsrs, doc(cfg(feature = "dns-over-native-tls")))] pub mod native_tls; pub mod op; #[cfg(feature = "dns-over-openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "dns-over-openssl")))] pub mod openssl; #[cfg(all(feature = "dns-over-quic", feature = "tokio-runtime"))] #[cfg_attr( docsrs, doc(cfg(all(feature = "dns-over-quic", feature = "tokio-runtime"))) )] pub mod quic; pub mod rr; #[cfg(feature = "dns-over-rustls")] #[cfg_attr(docsrs, doc(cfg(feature = "dns-over-rustls")))] pub mod rustls; pub mod serialize; pub mod tcp; #[cfg(any(test, feature = "testing"))] #[cfg_attr(docsrs, doc(cfg(feature = "testing")))] pub mod tests; pub mod udp; pub mod xfer; #[doc(hidden)] pub use crate::xfer::dns_handle::{DnsHandle, DnsStreamHandle}; #[doc(hidden)] pub use crate::xfer::dns_multiplexer::DnsMultiplexer; #[doc(hidden)] #[cfg(feature = "dnssec")] pub use crate::xfer::dnssec_dns_handle::DnssecDnsHandle; #[doc(hidden)] pub use crate::xfer::retry_dns_handle::RetryDnsHandle; #[doc(hidden)] pub use crate::xfer::BufDnsStreamHandle; #[cfg(feature = "backtrace")] #[cfg_attr(docsrs, doc(cfg(feature = "backtrace")))] pub use error::ExtBacktrace; #[cfg(feature = "tokio-runtime")] #[doc(hidden)] pub mod iocompat { use std::io; use std::pin::Pin; use std::task::{Context, Poll}; use futures_io::{AsyncRead, AsyncWrite}; use tokio::io::{AsyncRead as TokioAsyncRead, AsyncWrite as TokioAsyncWrite, ReadBuf}; /// Conversion from `tokio::io::{AsyncRead, AsyncWrite}` to `std::io::{AsyncRead, AsyncWrite}` pub struct AsyncIoTokioAsStd(pub T); impl Unpin for AsyncIoTokioAsStd {} impl AsyncRead for AsyncIoTokioAsStd { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let mut buf = ReadBuf::new(buf); let polled = Pin::new(&mut self.0).poll_read(cx, &mut buf); polled.map_ok(|_| buf.filled().len()) } } impl AsyncWrite for AsyncIoTokioAsStd { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.0).poll_write(cx, buf) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.0).poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.0).poll_shutdown(cx) } } /// Conversion from `std::io::{AsyncRead, AsyncWrite}` to `tokio::io::{AsyncRead, AsyncWrite}` pub struct AsyncIoStdAsTokio(pub T); impl Unpin for AsyncIoStdAsTokio {} impl TokioAsyncRead for AsyncIoStdAsTokio { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { Pin::new(&mut self.get_mut().0) .poll_read(cx, buf.initialized_mut()) .map_ok(|len| buf.advance(len)) } } impl TokioAsyncWrite for AsyncIoStdAsTokio { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.get_mut().0).poll_write(cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.get_mut().0).poll_flush(cx) } fn poll_shutdown( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { Pin::new(&mut self.get_mut().0).poll_close(cx) } } } /// Generic executor. // This trait is created to facilitate running the tests defined in the tests mod using different types of // executors. It's used in Fuchsia OS, please be mindful when update it. pub trait Executor { /// Create the implementor itself. fn new() -> Self; /// Spawns a future object to run synchronously or asynchronously depending on the specific /// executor. fn block_on(&mut self, future: F) -> F::Output; } #[cfg(feature = "tokio-runtime")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio-runtime")))] impl Executor for Runtime { fn new() -> Self { Self::new().expect("failed to create tokio runtime") } fn block_on(&mut self, future: F) -> F::Output { Self::block_on(self, future) } } /// Generic Time for Delay and Timeout. // This trait is created to allow to use different types of time systems. It's used in Fuchsia OS, please be mindful when update it. #[async_trait] pub trait Time { /// Return a type that implements `Future` that will wait until the specified duration has /// elapsed. async fn delay_for(duration: Duration); /// Return a type that implement `Future` to complete before the specified duration has elapsed. async fn timeout( duration: Duration, future: F, ) -> Result; } /// New type which is implemented using tokio::time::{Delay, Timeout} #[cfg(any(test, feature = "tokio-runtime"))] #[cfg_attr(docsrs, doc(cfg(feature = "tokio-runtime")))] #[derive(Clone, Copy, Debug)] pub struct TokioTime; #[cfg(any(test, feature = "tokio-runtime"))] #[cfg_attr(docsrs, doc(cfg(feature = "tokio-runtime")))] #[async_trait] impl Time for TokioTime { async fn delay_for(duration: Duration) { tokio::time::sleep(duration).await } async fn timeout( duration: Duration, future: F, ) -> Result { tokio::time::timeout(duration, future) .await .map_err(move |_| std::io::Error::new(std::io::ErrorKind::TimedOut, "future timed out")) } } hickory-proto-0.24.0/src/multicast/mdns_client_stream.rs000064400000000000000000000102631046102023000215360ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::fmt::{self, Display}; use std::net::{Ipv4Addr, SocketAddr}; use std::pin::Pin; use std::task::{Context, Poll}; use futures_util::future::{Future, FutureExt, TryFutureExt}; use futures_util::stream::{Stream, StreamExt, TryStreamExt}; use crate::error::ProtoError; use crate::multicast::mdns_stream::{MDNS_IPV4, MDNS_IPV6}; use crate::multicast::{MdnsQueryType, MdnsStream}; use crate::xfer::{DnsClientStream, SerialMessage}; use crate::{BufDnsStreamHandle, TokioTime}; /// A UDP client stream of DNS binary packets #[must_use = "futures do nothing unless polled"] pub struct MdnsClientStream { mdns_stream: MdnsStream, } impl MdnsClientStream { /// associates the socket to the well-known ipv4 multicast address pub fn new_ipv4( mdns_query_type: MdnsQueryType, packet_ttl: Option, ipv4_if: Option, ) -> (MdnsClientConnect, BufDnsStreamHandle) { Self::new(*MDNS_IPV4, mdns_query_type, packet_ttl, ipv4_if, None) } /// associates the socket to the well-known ipv6 multicast address pub fn new_ipv6( mdns_query_type: MdnsQueryType, packet_ttl: Option, ipv6_if: Option, ) -> (MdnsClientConnect, BufDnsStreamHandle) { Self::new(*MDNS_IPV6, mdns_query_type, packet_ttl, None, ipv6_if) } /// it is expected that the resolver wrapper will be responsible for creating and managing /// new UdpClients such that each new client would have a random port (reduce chance of cache /// poisoning) /// /// # Return /// /// a tuple of a Future Stream which will handle sending and receiving messages, and a /// handle which can be used to send messages into the stream. #[allow(clippy::new_ret_no_self)] pub fn new( mdns_addr: SocketAddr, mdns_query_type: MdnsQueryType, packet_ttl: Option, ipv4_if: Option, ipv6_if: Option, ) -> (MdnsClientConnect, BufDnsStreamHandle) { let (stream_future, sender) = MdnsStream::new(mdns_addr, mdns_query_type, packet_ttl, ipv4_if, ipv6_if); let stream_future = stream_future .map_ok(move |mdns_stream| Self { mdns_stream }) .map_err(ProtoError::from); let new_future = Box::new(stream_future); let new_future = MdnsClientConnect(new_future); (new_future, sender) } } impl Display for MdnsClientStream { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(formatter, "mDNS({})", self.mdns_stream.multicast_addr()) } } impl DnsClientStream for MdnsClientStream { type Time = TokioTime; fn name_server_addr(&self) -> SocketAddr { self.mdns_stream.multicast_addr() } } impl Stream for MdnsClientStream { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mdns_stream = &mut self.as_mut().mdns_stream; mdns_stream.map_err(ProtoError::from).poll_next_unpin(cx) // match ready!(self.mdns_stream.poll_next_unpin(cx).map_err(ProtoError::from)) { // Some(serial_message) => { // // TODO: for mDNS queries could come from anywhere. It's not clear that there is anything // // we can validate in this case. // Poll::Ready(Some(Ok(serial_message))) // } // None => Poll::Ready(None), // } } } /// A future that resolves to an MdnsClientStream pub struct MdnsClientConnect( Box> + Send + Unpin>, ); impl Future for MdnsClientConnect { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.0.as_mut().poll_unpin(cx) } } hickory-proto-0.24.0/src/multicast/mdns_stream.rs000064400000000000000000000715511046102023000202070ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std; use std::io; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use futures_util::stream::{Stream, StreamExt}; use futures_util::{future, future::Future, ready, FutureExt, TryFutureExt}; use once_cell::sync::Lazy; use rand; use rand::distributions::{uniform::Uniform, Distribution}; use socket2::{self, Socket}; use tokio::net::UdpSocket; use tracing::{debug, trace}; use crate::multicast::MdnsQueryType; use crate::udp::UdpStream; use crate::xfer::SerialMessage; use crate::BufDnsStreamHandle; pub(crate) const MDNS_PORT: u16 = 5353; /// mDNS ipv4 address https://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml pub static MDNS_IPV4: Lazy = Lazy::new(|| SocketAddr::new(Ipv4Addr::new(224, 0, 0, 251).into(), MDNS_PORT)); /// link-local mDNS ipv6 address https://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml pub static MDNS_IPV6: Lazy = Lazy::new(|| { SocketAddr::new( Ipv6Addr::new(0xFF02, 0, 0, 0, 0, 0, 0, 0x00FB).into(), MDNS_PORT, ) }); /// A UDP stream of DNS binary packets #[must_use = "futures do nothing unless polled"] pub struct MdnsStream { /// Multicast address used for mDNS queries multicast_addr: SocketAddr, /// This is used for sending and (directly) receiving messages datagram: Option>, // FIXME: like UdpStream, this Arc is unnecessary, only needed for temp async/await capture below /// In one-shot multicast, this will not join the multicast group multicast: Option>, /// Receiving portion of the MdnsStream rcving_mcast: Option> + Send>>>, } impl MdnsStream { /// associates the socket to the well-known ipv4 multicast address pub fn new_ipv4( mdns_query_type: MdnsQueryType, packet_ttl: Option, ipv4_if: Option, ) -> ( Box> + Send + Unpin>, BufDnsStreamHandle, ) { Self::new(*MDNS_IPV4, mdns_query_type, packet_ttl, ipv4_if, None) } /// associates the socket to the well-known ipv6 multicast address pub fn new_ipv6( mdns_query_type: MdnsQueryType, packet_ttl: Option, ipv6_if: Option, ) -> ( Box> + Send + Unpin>, BufDnsStreamHandle, ) { Self::new(*MDNS_IPV6, mdns_query_type, packet_ttl, None, ipv6_if) } /// Returns the address of the multicast network in use pub fn multicast_addr(&self) -> SocketAddr { self.multicast_addr } /// This method is available for specifying a custom Multicast address to use. /// /// In general this operates nearly identically to UDP, except that it automatically joins /// the default multicast DNS addresses. See /// for details. /// /// When sending ipv6 multicast packets, the interface being used is required, /// this will panic if the interface is not specified for all MdnsQueryType except Passive /// (which does not allow sending data) /// /// # Arguments /// /// * `multicast_addr` - address to use for multicast requests /// * `mdns_query_type` - true if the querier using this socket will only perform standard DNS queries over multicast. /// * `ipv4_if` - Address to bind to for sending multicast packets, defaults to `0.0.0.0` if not specified (not relevant for ipv6) /// * `ipv6_if` - Interface index for the interface to be used when sending ipv6 packets. /// /// # Return /// /// a tuple of a Future Stream which will handle sending and receiving messages, and a /// handle which can be used to send messages into the stream. pub fn new( multicast_addr: SocketAddr, mdns_query_type: MdnsQueryType, packet_ttl: Option, ipv4_if: Option, ipv6_if: Option, ) -> ( Box> + Send + Unpin>, BufDnsStreamHandle, ) { let (message_sender, outbound_messages) = BufDnsStreamHandle::new(multicast_addr); let multicast_socket = match Self::join_multicast(&multicast_addr, mdns_query_type) { Ok(socket) => socket, Err(err) => return (Box::new(future::err(err)), message_sender), }; // TODO: allow the bind address to be specified... // constructs a future for getting the next randomly bound port to a UdpSocket let next_socket = Self::next_bound_local_address( &multicast_addr, mdns_query_type, packet_ttl, ipv4_if, ipv6_if, ); // while 0 is meant to keep the packet on localhost, linux regards this as an error, // while macOS (BSD?) and Windows allow it. if let Some(ttl) = packet_ttl { assert!(ttl > 0, "TTL must be greater than 0"); } // This set of futures collapses the next udp socket into a stream which can be used for // sending and receiving udp packets. let stream = { Box::new( next_socket .map(move |socket| match socket { Ok(Some(socket)) => Ok(Some(UdpSocket::from_std(socket)?)), Ok(None) => Ok(None), Err(err) => Err(err), }) .map_ok(move |socket: Option<_>| { let datagram: Option<_> = socket.map(|socket| UdpStream::from_parts(socket, outbound_messages)); let multicast: Option<_> = multicast_socket.map(|multicast_socket| { Arc::new(UdpSocket::from_std(multicast_socket).expect("bad handle?")) }); Self { multicast_addr, datagram, multicast, rcving_mcast: None, } }), ) }; (stream, message_sender) } /// On Windows, unlike all Unix variants, it is improper to bind to the multicast address /// /// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms737550(v=vs.85).aspx #[cfg(windows)] #[cfg_attr(docsrs, doc(cfg(windows)))] fn bind_multicast(socket: &Socket, multicast_addr: &SocketAddr) -> io::Result<()> { let multicast_addr = match *multicast_addr { SocketAddr::V4(addr) => SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), addr.port()), SocketAddr::V6(addr) => { SocketAddr::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0).into(), addr.port()) } }; socket.bind(&socket2::SockAddr::from(multicast_addr)) } /// On unixes we bind to the multicast address, which causes multicast packets to be filtered #[cfg(unix)] #[cfg_attr(docsrs, doc(cfg(unix)))] fn bind_multicast(socket: &Socket, multicast_addr: &SocketAddr) -> io::Result<()> { socket.bind(&socket2::SockAddr::from(*multicast_addr)) } /// Returns a socket joined to the multicast address fn join_multicast( multicast_addr: &SocketAddr, mdns_query_type: MdnsQueryType, ) -> Result, io::Error> { if !mdns_query_type.join_multicast() { return Ok(None); } let ip_addr = multicast_addr.ip(); // it's an error to not use a proper mDNS address if !ip_addr.is_multicast() { return Err(io::Error::new( io::ErrorKind::Other, format!("expected multicast address for binding: {ip_addr}"), )); } // binding the UdpSocket to the multicast address tells the OS to filter all packets on this socket to just this // multicast address // TODO: allow the binding interface to be specified let socket = match ip_addr { IpAddr::V4(ref mdns_v4) => { let socket = Socket::new( socket2::Domain::IPV4, socket2::Type::DGRAM, Some(socket2::Protocol::UDP), )?; socket.join_multicast_v4(mdns_v4, &Ipv4Addr::new(0, 0, 0, 0))?; socket } IpAddr::V6(ref mdns_v6) => { let socket = Socket::new( socket2::Domain::IPV6, socket2::Type::DGRAM, Some(socket2::Protocol::UDP), )?; socket.set_only_v6(true)?; socket.join_multicast_v6(mdns_v6, 0)?; socket } }; socket.set_nonblocking(true)?; socket.set_reuse_address(true)?; #[cfg(unix)] // this is currently restricted to Unix's in socket2 socket.set_reuse_port(true)?; Self::bind_multicast(&socket, multicast_addr)?; debug!("joined {multicast_addr}"); Ok(Some(std::net::UdpSocket::from(socket))) } /// Creates a future for randomly binding to a local socket address for client connections. fn next_bound_local_address( multicast_addr: &SocketAddr, mdns_query_type: MdnsQueryType, packet_ttl: Option, ipv4_if: Option, ipv6_if: Option, ) -> NextRandomUdpSocket { let bind_address: IpAddr = match *multicast_addr { SocketAddr::V4(..) => IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), SocketAddr::V6(..) => IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), }; NextRandomUdpSocket { bind_address, mdns_query_type, packet_ttl, ipv4_if, ipv6_if, } } } impl Stream for MdnsStream { type Item = io::Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { assert!(self.datagram.is_some() || self.multicast.is_some()); // we poll the datagram socket first, if available, since it's a direct response or direct request if let Some(ref mut datagram) = self.as_mut().datagram { match datagram.poll_next_unpin(cx) { Poll::Ready(ready) => return Poll::Ready(ready), Poll::Pending => (), // drop through } } loop { let msg = if let Some(ref mut receiving) = self.rcving_mcast { // TODO: should we drop this packet if it's not from the same src as dest? let msg = ready!(receiving.as_mut().poll_unpin(cx))?; Some(Poll::Ready(Some(Ok(msg)))) } else { None }; self.rcving_mcast = None; if let Some(msg) = msg { return msg; } // let socket = Arc::clone(socket); if let Some(ref socket) = self.multicast { let socket = Arc::clone(socket); let receive_future = async { let socket = socket; let mut buf = [0u8; 2048]; let (len, src) = socket.recv_from(&mut buf).await?; Ok(SerialMessage::new( buf.iter().take(len).cloned().collect(), src, )) }; self.rcving_mcast = Some(Box::pin(receive_future.boxed())); } } } } #[must_use = "futures do nothing unless polled"] struct NextRandomUdpSocket { bind_address: IpAddr, mdns_query_type: MdnsQueryType, packet_ttl: Option, ipv4_if: Option, ipv6_if: Option, } impl NextRandomUdpSocket { fn prepare_sender(&self, socket: std::net::UdpSocket) -> io::Result { let addr = socket.local_addr()?; debug!("preparing sender on: {addr}"); let socket = Socket::from(socket); // TODO: TTL doesn't work on ipv6 match addr { SocketAddr::V4(..) => { socket.set_multicast_loop_v4(true)?; socket.set_multicast_if_v4( &self.ipv4_if.unwrap_or_else(|| Ipv4Addr::new(0, 0, 0, 0)), )?; if let Some(ttl) = self.packet_ttl { socket.set_ttl(ttl)?; socket.set_multicast_ttl_v4(ttl)?; } } SocketAddr::V6(..) => { let ipv6_if = self.ipv6_if.unwrap_or_else(|| { panic!("for ipv6 multicasting the interface must be specified") }); socket.set_multicast_loop_v6(true)?; socket.set_multicast_if_v6(ipv6_if)?; if let Some(ttl) = self.packet_ttl { socket.set_unicast_hops_v6(ttl)?; socket.set_multicast_hops_v6(ttl)?; } } } Ok(std::net::UdpSocket::from(socket)) } } impl Future for NextRandomUdpSocket { // TODO: clean this up, the RandomUdpSocket shouldn't care about the query type type Output = io::Result>; /// polls until there is an available next random UDP port. /// /// if there is no port available after 10 attempts, returns NotReady fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // non-one-shot, i.e. continuous, always use one of the well-known mdns ports and bind to the multicast addr if !self.mdns_query_type.sender() { debug!("skipping sending stream"); Poll::Ready(Ok(None)) } else if self.mdns_query_type.bind_on_5353() { let addr = SocketAddr::new(self.bind_address, MDNS_PORT); debug!("binding sending stream to {}", addr); let socket = std::net::UdpSocket::bind(addr)?; let socket = self.prepare_sender(socket)?; Poll::Ready(Ok(Some(socket))) } else { // TODO: this is basically identical to UdpStream from here... share some code? (except for the port restriction) // one-shot queries look very similar to UDP socket, but can't listen on 5353 // Per RFC 6056 Section 2.1: // // The dynamic port range defined by IANA consists of the 49152-65535 // range, and is meant for the selection of ephemeral ports. let rand_port_range = Uniform::new_inclusive(49152_u16, u16::max_value()); let mut rand = rand::thread_rng(); for attempt in 0..10 { let port = rand_port_range.sample(&mut rand); // see one_shot usage info: https://tools.ietf.org/html/rfc6762#section-5 // the MDNS_PORT is used to signal to remote processes that this is capable of receiving multicast packets // i.e. is joined to the multicast address. if port == MDNS_PORT { trace!("unlucky, got MDNS_PORT"); continue; } let addr = SocketAddr::new(self.bind_address, port); debug!("binding sending stream to {}", addr); match std::net::UdpSocket::bind(addr) { Ok(socket) => { let socket = self.prepare_sender(socket)?; return Poll::Ready(Ok(Some(socket))); } Err(err) => debug!("unable to bind port, attempt: {}: {}", attempt, err), } } debug!("could not get next random port, delaying"); // TODO: this replaced a task::current().notify, is it correct? cx.waker().wake_by_ref(); Poll::Pending } } } #[cfg(test)] pub(crate) mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; use crate::xfer::dns_handle::DnsStreamHandle; use futures_util::future::Either; use tokio::runtime; // TODO: is there a better way? const BASE_TEST_PORT: u16 = 5379; /// 250 appears to be unused/unregistered static TEST_MDNS_IPV4: Lazy = Lazy::new(|| Ipv4Addr::new(224, 0, 0, 250).into()); /// FA appears to be unused/unregistered static TEST_MDNS_IPV6: Lazy = Lazy::new(|| Ipv6Addr::new(0xFF02, 0, 0, 0, 0, 0, 0, 0x00FA).into()); // one_shot tests are basically clones from the udp tests #[test] fn test_next_random_socket() { // use env_logger; // env_logger::init(); let io_loop = runtime::Runtime::new().unwrap(); let (stream, _) = MdnsStream::new( SocketAddr::new(*TEST_MDNS_IPV4, BASE_TEST_PORT), MdnsQueryType::OneShot, Some(1), None, None, ); let result = io_loop.block_on(stream); if let Err(error) = result { println!("Random address error: {error:#?}"); panic!("failed to get next random address"); } } // FIXME: reenable after breakage in async/await #[ignore] #[test] fn test_one_shot_mdns_ipv4() { one_shot_mdns_test(SocketAddr::new(*TEST_MDNS_IPV4, BASE_TEST_PORT + 1)); } #[test] #[ignore] fn test_one_shot_mdns_ipv6() { one_shot_mdns_test(SocketAddr::new(*TEST_MDNS_IPV6, BASE_TEST_PORT + 2)); } // as there are probably unexpected responses coming on the standard addresses fn one_shot_mdns_test(mdns_addr: SocketAddr) { use std::time::Duration; let client_done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); let test_bytes: &'static [u8; 8] = b"DEADBEEF"; let send_recv_times = 10; let client_done_clone = client_done.clone(); // an in and out server let server_handle = std::thread::Builder::new() .name("test_one_shot_mdns:server".to_string()) .spawn(move || { let server_loop = runtime::Runtime::new().unwrap(); let mut timeout = future::lazy(|_| tokio::time::sleep(Duration::from_millis(100))) .flatten() .boxed(); // TTLs are 0 so that multicast test packets never leave the test host... // FIXME: this is hardcoded to index 5 for ipv6, which isn't going to be correct in most cases... let (server_stream_future, mut server_sender) = MdnsStream::new( mdns_addr, MdnsQueryType::OneShotJoin, Some(1), None, Some(5), ); // For one-shot responses we are competing with a system mDNS responder, we will respond from a different port... let mut server_stream = server_loop .block_on(server_stream_future) .expect("could not create mDNS listener") .into_future(); for _ in 0..=send_recv_times { if client_done_clone.load(std::sync::atomic::Ordering::Relaxed) { return; } // wait for some bytes... match server_loop.block_on( future::lazy(|_| future::select(server_stream, timeout)).flatten(), ) { Either::Left((buffer_and_addr_stream_tmp, timeout_tmp)) => { let (buffer_and_addr, stream_tmp): ( Option>, MdnsStream, ) = buffer_and_addr_stream_tmp; server_stream = stream_tmp.into_future(); timeout = timeout_tmp; let (buffer, addr) = buffer_and_addr .expect("no msg received") .expect("error receiving msg") .into_parts(); assert_eq!(&buffer, test_bytes); //println!("server got data! {}", addr); // bounce them right back... server_sender .send(SerialMessage::new(test_bytes.to_vec(), addr)) .expect("could not send to client"); } Either::Right(((), buffer_and_addr_stream_tmp)) => { server_stream = buffer_and_addr_stream_tmp; timeout = future::lazy(|_| tokio::time::sleep(Duration::from_millis(100))) .flatten() .boxed(); } } // let the server turn for a bit... send the message server_loop.block_on(tokio::time::sleep(Duration::from_millis(100))); } }) .unwrap(); // setup the client, which is going to run on the testing thread... let io_loop = runtime::Runtime::new().unwrap(); // FIXME: this is hardcoded to index 5 for ipv6, which isn't going to be correct in most cases... let (stream, mut sender) = MdnsStream::new(mdns_addr, MdnsQueryType::OneShot, Some(1), None, Some(5)); let mut stream = io_loop.block_on(stream).ok().unwrap().into_future(); let mut timeout = future::lazy(|_| tokio::time::sleep(Duration::from_millis(100))) .flatten() .boxed(); let mut successes = 0; for _ in 0..send_recv_times { // test once sender .send(SerialMessage::new(test_bytes.to_vec(), mdns_addr)) .unwrap(); println!("client sending data!"); // TODO: this lazy isn't needed is it? match io_loop.block_on(future::lazy(|_| future::select(stream, timeout)).flatten()) { Either::Left((buffer_and_addr_stream_tmp, timeout_tmp)) => { let (buffer_and_addr, stream_tmp) = buffer_and_addr_stream_tmp; stream = stream_tmp.into_future(); timeout = timeout_tmp; let (buffer, _addr) = buffer_and_addr .expect("no msg received") .expect("error receiving msg") .into_parts(); println!("client got data!"); assert_eq!(&buffer, test_bytes); successes += 1; } Either::Right(((), buffer_and_addr_stream_tmp)) => { stream = buffer_and_addr_stream_tmp; timeout = future::lazy(|_| tokio::time::sleep(Duration::from_millis(100))) .flatten() .boxed(); } } } client_done.store(true, std::sync::atomic::Ordering::Relaxed); println!("successes: {successes}"); assert!(successes >= 1); server_handle.join().expect("server thread failed"); } // FIXME: reenable after breakage in async/await #[ignore] #[test] fn test_passive_mdns() { passive_mdns_test( MdnsQueryType::Passive, SocketAddr::new(*TEST_MDNS_IPV4, BASE_TEST_PORT + 3), ) } // FIXME: reenable after breakage in async/await #[ignore] #[test] fn test_oneshot_join_mdns() { passive_mdns_test( MdnsQueryType::OneShotJoin, SocketAddr::new(*TEST_MDNS_IPV4, BASE_TEST_PORT + 4), ) } // as there are probably unexpected responses coming on the standard addresses fn passive_mdns_test(mdns_query_type: MdnsQueryType, mdns_addr: SocketAddr) { use std::time::Duration; let server_got_packet = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); let test_bytes: &'static [u8; 8] = b"DEADBEEF"; let send_recv_times = 10; let server_got_packet_clone = server_got_packet.clone(); // an in and out server let _server_handle = std::thread::Builder::new() .name("test_one_shot_mdns:server".to_string()) .spawn(move || { let io_loop = runtime::Runtime::new().unwrap(); let mut timeout = future::lazy(|_| tokio::time::sleep(Duration::from_millis(100))) .flatten() .boxed(); // TTLs are 0 so that multicast test packets never leave the test host... // FIXME: this is hardcoded to index 5 for ipv6, which isn't going to be correct in most cases... let (server_stream_future, _server_sender) = MdnsStream::new(mdns_addr, mdns_query_type, Some(1), None, Some(5)); // For one-shot responses we are competing with a system mDNS responder, we will respond from a different port... let mut server_stream = io_loop .block_on(server_stream_future) .expect("could not create mDNS listener") .into_future(); for _ in 0..=send_recv_times { // wait for some bytes... match io_loop.block_on( future::lazy(|_| future::select(server_stream, timeout)).flatten(), ) { Either::Left((_buffer_and_addr_stream_tmp, _timeout_tmp)) => { // let (buffer_and_addr, stream_tmp) = buffer_and_addr_stream_tmp; // server_stream = stream_tmp.into_future(); // timeout = timeout_tmp; // let (buffer, addr) = buffer_and_addr.expect("no buffer received"); // assert_eq!(&buffer, test_bytes); // println!("server got data! {}", addr); server_got_packet_clone .store(true, std::sync::atomic::Ordering::Relaxed); return; } Either::Right(((), buffer_and_addr_stream_tmp)) => { server_stream = buffer_and_addr_stream_tmp; timeout = future::lazy(|_| tokio::time::sleep(Duration::from_millis(100))) .flatten() .boxed(); } } // let the server turn for a bit... send the message io_loop.block_on(tokio::time::sleep(Duration::from_millis(100))); } }) .unwrap(); // setup the client, which is going to run on the testing thread... let io_loop = runtime::Runtime::new().unwrap(); // FIXME: this is hardcoded to index 5 for ipv6, which isn't going to be correct in most cases... let (stream, mut sender) = MdnsStream::new(mdns_addr, MdnsQueryType::OneShot, Some(1), None, Some(5)); let mut stream = io_loop.block_on(stream).ok().unwrap().into_future(); let mut timeout = future::lazy(|_| tokio::time::sleep(Duration::from_millis(100))) .flatten() .boxed(); for _ in 0..send_recv_times { // test once sender .send(SerialMessage::new(test_bytes.to_vec(), mdns_addr)) .unwrap(); println!("client sending data!"); // TODO: this lazy is probably unnecessary? let run_result = io_loop.block_on(future::lazy(|_| future::select(stream, timeout)).flatten()); if server_got_packet.load(std::sync::atomic::Ordering::Relaxed) { return; } match run_result { Either::Left((buffer_and_addr_stream_tmp, timeout_tmp)) => { let (_buffer_and_addr, stream_tmp) = buffer_and_addr_stream_tmp; stream = stream_tmp.into_future(); timeout = timeout_tmp; } Either::Right(((), buffer_and_addr_stream_tmp)) => { stream = buffer_and_addr_stream_tmp; timeout = future::lazy(|_| tokio::time::sleep(Duration::from_millis(100))) .flatten() .boxed(); } } } panic!("server never got packet."); } } hickory-proto-0.24.0/src/multicast/mod.rs000064400000000000000000000065011046102023000164430ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Multicast protocol related components for DNS #[cfg(feature = "tokio-runtime")] mod mdns_client_stream; #[cfg(feature = "tokio-runtime")] mod mdns_stream; #[cfg(feature = "tokio-runtime")] pub use self::mdns_client_stream::{MdnsClientConnect, MdnsClientStream}; #[cfg(feature = "tokio-runtime")] pub use self::mdns_stream::{MdnsStream, MDNS_IPV4, MDNS_IPV6}; /// See [rfc6762](https://tools.ietf.org/html/rfc6762#section-5) details on these different types. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum MdnsQueryType { /// The querier using this socket will only perform standard DNS queries over multicast. (clients only) /// /// Effectively treats mDNS as essentially no different than any other DNS query; one request followed by one response. /// Only one UDP socket will be created. OneShot, /// The querier is fully compliant with [rfc6762](https://tools.ietf.org/html/rfc6762#section-5). (servers, clients) /// /// mDNS capable clients will sent messages with many queries, and they will expect many responses. Two UDP sockets will be /// created, one for receiving multicast traffic, the other used for sending queries and direct responses. This requires /// port 5353 to be available on the system (many modern OSes already have mDNSResponders running taking this port). Continuous, /// The querier operates under the OneShot semantics, but also joins the multicast group. (non-compliant servers, clients) /// /// This is not defined in the mDNS RFC, but allows for a multicast client to join the group, receiving all multicast network /// traffic. This is useful where listening for all mDNS traffic is of interest, but because another mDNS process may have /// already taken the known port, 5353. Query responses will come from and to the standard UDP socket with a random port, /// multicast traffic will come from the multicast socket. This will create two sockets. OneShotJoin, /// The querier operates under the OneShot semantics, but also joins the multicast group. (servers) /// /// Not defined in the RFC, allows for a passive listener to receive all mDNS traffic. Passive, } impl MdnsQueryType { /// This will be sending packets, i.e. a standard UDP socket will be created pub fn sender(self) -> bool { match self { Self::Passive => false, Self::OneShot | Self::OneShotJoin => true, Self::Continuous => true, } } /// Returns true if this process can bind to *:5353 pub fn bind_on_5353(self) -> bool { match self { Self::OneShot | Self::OneShotJoin | Self::Passive => false, Self::Continuous => true, } } /// Returns true if this mDNS client should join, listen, on the multicast address pub fn join_multicast(self) -> bool { match self { Self::OneShot => false, Self::Continuous | Self::OneShotJoin | Self::Passive => true, } } } hickory-proto-0.24.0/src/native_tls/mod.rs000064400000000000000000000011511046102023000166020ustar 00000000000000// Copyright 2015-2021 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! TLS protocol related components for DNS over TLS pub mod tls_client_stream; pub mod tls_stream; pub use self::tls_client_stream::{TlsClientStream, TlsClientStreamBuilder}; pub use self::tls_stream::{TlsStream, TlsStreamBuilder}; #[cfg(test)] mod tests; hickory-proto-0.24.0/src/native_tls/tests.rs000064400000000000000000000215361046102023000171760ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. #![allow( unused_imports, clippy::dbg_macro, clippy::print_stdout, clippy::single_component_path_imports )] use std::env; use std::fs::File; use std::io::{Read, Write}; #[cfg(not(target_os = "linux"))] use std::net::Ipv6Addr; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; use std::sync::atomic; use std::sync::Arc; use std::{thread, time}; use futures_util::stream::StreamExt; use native_tls; use native_tls::{Certificate, TlsAcceptor}; use tokio::net::TcpStream as TokioTcpStream; use tokio::runtime::Runtime; #[allow(clippy::useless_attribute)] #[allow(unused)] use crate::native_tls::{TlsStream, TlsStreamBuilder}; use crate::xfer::SerialMessage; use crate::{iocompat::AsyncIoTokioAsStd, DnsStreamHandle}; // this fails on linux for some reason. It appears that a buffer somewhere is dirty // and subsequent reads of a message buffer reads the wrong length. It works for 2 iterations // but not 3? // #[cfg(not(target_os = "linux"))] #[test] #[cfg_attr(target_os = "macos", ignore)] // TODO: add back once https://github.com/sfackler/rust-native-tls/issues/143 is fixed fn test_tls_client_stream_ipv4() { tls_client_stream_test(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), false) } // FIXME: mtls is disabled at the moment, it causes a hang on Linux, and is currently not supported on macOS #[cfg(feature = "mtls")] #[test] #[cfg(not(target_os = "macos"))] fn test_tls_client_stream_ipv4_mtls() { tls_client_stream_test(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), true) } #[test] #[cfg_attr(target_os = "macos", ignore)] // TODO: add back once https://github.com/sfackler/rust-native-tls/issues/143 is fixed #[cfg(not(target_os = "linux"))] // ignored until Travis-CI fixes IPv6 #[cfg(not(target_os = "macos"))] // certificates are failing on macOS now fn test_tls_client_stream_ipv6() { tls_client_stream_test(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), false) } const TEST_BYTES: &[u8; 8] = b"DEADBEEF"; const TEST_BYTES_LEN: usize = 8; fn read_file(path: &str) -> Vec { let mut bytes = vec![]; let mut file = File::open(path).unwrap_or_else(|_| panic!("failed to open file: {}", path)); file.read_to_end(&mut bytes) .unwrap_or_else(|_| panic!("failed to read file: {}", path)); bytes } #[allow(unused, unused_mut)] fn tls_client_stream_test(server_addr: IpAddr, mtls: bool) { let succeeded = Arc::new(atomic::AtomicBool::new(false)); let succeeded_clone = succeeded.clone(); thread::Builder::new() .name("thread_killer".to_string()) .spawn(move || { let succeeded = succeeded_clone; for _ in 0..15 { thread::sleep(time::Duration::from_secs(1)); if succeeded.load(atomic::Ordering::Relaxed) { return; } } println!("Thread Killer has been awoken, killing process"); std::process::exit(-1); }) .unwrap(); let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "../..".to_owned()); println!("using server src path: {server_path}"); let root_cert_der = read_file(&format!("{server_path}/tests/test-data/ca.der")); // Generate X509 certificate let dns_name = "ns.example.com"; let server_pkcs12_der = read_file(&format!("{server_path}/tests/test-data/cert.p12")); // TODO: need a timeout on listen let server = std::net::TcpListener::bind(SocketAddr::new(server_addr, 0)).unwrap(); let server_addr = server.local_addr().unwrap(); let send_recv_times = 4; let server_handle = thread::Builder::new() .name("test_tls_client_stream:server".to_string()) .spawn(move || { let pkcs12 = native_tls::Identity::from_pkcs12(&server_pkcs12_der, "mypass") .expect("Identity::from_pkcs12"); let mut tls = TlsAcceptor::builder(pkcs12); // #[cfg(target_os = "linux")] // { // let mut openssl_builder = tls.builder_mut(); // let mut openssl_ctx_builder = openssl_builder.builder_mut(); // let mut mode = openssl::ssl::SslVerifyMode::empty(); // // TODO: mtls tests hang on Linux... // if mtls { // // mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; // // let mut store = X509StoreBuilder::new().unwrap(); // // let root_ca = X509::from_der(&root_cert_der_copy).unwrap(); // // store.add_cert(root_ca).unwrap(); // // openssl_ctx_builder.set_verify_cert_store(store.build()).unwrap(); // } else { // mode.insert(SSL_VERIFY_NONE); // } // openssl_ctx_builder.set_verify(mode); // } // TODO: add CA on macOS let tls = tls.build().expect("tls build failed"); // server_barrier.wait(); let (socket, _) = server.accept().expect("tcp accept failed"); socket .set_read_timeout(Some(std::time::Duration::from_secs(5))) .unwrap(); // should receive something within 5 seconds... socket .set_write_timeout(Some(std::time::Duration::from_secs(5))) .unwrap(); // should receive something within 5 seconds... let mut socket = tls.accept(socket).expect("tls accept failed"); for _ in 0..send_recv_times { // wait for some bytes... let mut len_bytes = [0_u8; 2]; socket .read_exact(&mut len_bytes) .expect("SERVER: receive failed"); let length = u16::from(len_bytes[0]) << 8 & 0xFF00 | u16::from(len_bytes[1]) & 0x00FF; assert_eq!(length as usize, TEST_BYTES_LEN); let mut buffer = [0_u8; TEST_BYTES_LEN]; socket.read_exact(&mut buffer).unwrap(); // println!("read bytes iter: {}", i); assert_eq!(&buffer, TEST_BYTES); // bounce them right back... socket .write_all(&len_bytes) .expect("SERVER: send length failed"); socket .write_all(&buffer) .expect("SERVER: send buffer failed"); // println!("wrote bytes iter: {}", i); std::thread::yield_now(); } }) .unwrap(); // let the server go first std::thread::yield_now(); // setup the client, which is going to run on the testing thread... let mut io_loop = Runtime::new().unwrap(); // the tests should run within 5 seconds... right? // TODO: add timeout here, so that test never hangs... // let timeout = Timeout::new(Duration::from_secs(5)); let trust_chain = Certificate::from_der(&root_cert_der).unwrap(); // barrier.wait(); let mut builder = TlsStreamBuilder::>::new(); builder.add_ca(trust_chain); // fix MTLS // if mtls { // config_mtls(&root_pkey, &root_name, &root_cert, &mut builder); // } let (stream, mut sender) = builder.build(server_addr, dns_name.to_string()); // TODO: there is a race failure here... a race with the server thread most likely... let mut stream = io_loop.block_on(stream).expect("run failed to get stream"); for _ in 0..send_recv_times { // test once sender .send(SerialMessage::new(TEST_BYTES.to_vec(), server_addr)) .expect("send failed"); let (buffer, stream_tmp) = io_loop.block_on(stream.into_future()); stream = stream_tmp; let message = buffer.expect("no buffer received"); assert_eq!( message.expect("message destructure failed").bytes(), TEST_BYTES ); } succeeded.store(true, std::sync::atomic::Ordering::Relaxed); server_handle.join().expect("server thread failed"); } // TODO: fix MTLS // #[allow(unused_variables)] // fn config_mtls(root_pkey: &PKey, // root_name: &X509Name, // root_cert: &X509, // builder: &mut TlsStreamBuilder) { // // signed by the same root cert // let client_name = "resolv.example.com"; // let (_ /*client_pkey*/, _ /*client_cert*/, client_identity) = // cert(client_name, root_pkey, root_name, root_cert); // let client_identity = // native_tls::Pkcs12::from_der(&client_identity.to_der().unwrap(), "mypass").unwrap(); // #[cfg(feature = "mtls")] // builder.identity(client_identity); // } hickory-proto-0.24.0/src/native_tls/tls_client_stream.rs000064400000000000000000000076331046102023000215510ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! TlsClientStream for DNS over TLS use std::future::Future; use std::net::SocketAddr; use std::pin::Pin; use futures_util::TryFutureExt; use native_tls::Certificate; #[cfg(feature = "mtls")] use native_tls::Pkcs12; use tokio_native_tls::TlsStream as TokioTlsStream; use crate::error::ProtoError; use crate::iocompat::AsyncIoStdAsTokio; use crate::iocompat::AsyncIoTokioAsStd; use crate::native_tls::TlsStreamBuilder; use crate::tcp::{Connect, DnsTcpStream, TcpClientStream}; use crate::xfer::BufDnsStreamHandle; /// TlsClientStream secure DNS over TCP stream /// /// See TlsClientStreamBuilder::new() pub type TlsClientStream = TcpClientStream>>>; /// Builder for TlsClientStream pub struct TlsClientStreamBuilder(TlsStreamBuilder); impl TlsClientStreamBuilder { /// Creates a builder fo the construction of a TlsClientStream pub fn new() -> Self { Self(TlsStreamBuilder::new()) } /// Add a custom trusted peer certificate or certificate authority. /// /// If this is the 'client' then the 'server' must have it associated as it's `identity`, or have had the `identity` signed by this certificate. pub fn add_ca(&mut self, ca: Certificate) { self.0.add_ca(ca); } /// Client side identity for client auth in TLS (aka mutual TLS auth) #[cfg(feature = "mtls")] pub fn identity(&mut self, pkcs12: Pkcs12) { self.0.identity(pkcs12); } /// Sets the address to connect from. pub fn bind_addr(&mut self, bind_addr: SocketAddr) { self.0.bind_addr(bind_addr); } /// Creates a new TlsStream to the specified name_server with stream future. /// /// # Arguments /// /// * 'future` - future of TCP stream /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate #[allow(clippy::type_complexity)] pub fn build_with_future( self, future: F, name_server: SocketAddr, dns_name: String, ) -> ( Pin, ProtoError>> + Send>>, BufDnsStreamHandle, ) where F: Future> + Send + Unpin + 'static, { let (stream_future, sender) = self.0.build_with_future(future, name_server, dns_name); let new_future = Box::pin( stream_future .map_ok(TcpClientStream::from_stream) .map_err(ProtoError::from), ); (new_future, sender) } } impl TlsClientStreamBuilder { /// Creates a new TlsStream to the specified name_server /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate #[allow(clippy::type_complexity)] pub fn build( self, name_server: SocketAddr, dns_name: String, ) -> ( Pin, ProtoError>> + Send>>, BufDnsStreamHandle, ) { let (stream_future, sender) = self.0.build(name_server, dns_name); let new_future = Box::pin( stream_future .map_ok(TcpClientStream::from_stream) .map_err(ProtoError::from), ); (new_future, sender) } } impl Default for TlsClientStreamBuilder { fn default() -> Self { Self::new() } } hickory-proto-0.24.0/src/native_tls/tls_stream.rs000064400000000000000000000164221046102023000202070ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Base TlsStream use std::io; use std::net::SocketAddr; use std::pin::Pin; use std::{future::Future, marker::PhantomData}; use futures_util::TryFutureExt; use native_tls::Protocol::Tlsv12; use native_tls::{Certificate, Identity, TlsConnector}; use tokio_native_tls::{TlsConnector as TokioTlsConnector, TlsStream as TokioTlsStream}; use crate::iocompat::{AsyncIoStdAsTokio, AsyncIoTokioAsStd}; use crate::tcp::TcpStream; use crate::tcp::{Connect, DnsTcpStream}; use crate::xfer::{BufDnsStreamHandle, StreamReceiver}; /// A TlsStream counterpart to the TcpStream which embeds a secure TlsStream pub type TlsStream = TcpStream>>>; fn tls_new(certs: Vec, pkcs12: Option) -> io::Result { let mut builder = TlsConnector::builder(); builder.min_protocol_version(Some(Tlsv12)); for cert in certs { builder.add_root_certificate(cert); } if let Some(pkcs12) = pkcs12 { builder.identity(pkcs12); } builder .build() .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}"))) } /// Initializes a TlsStream with an existing tokio_tls::TlsStream. /// /// This is intended for use with a TlsListener and Incoming connections pub fn tls_from_stream( stream: TokioTlsStream>, peer_addr: SocketAddr, ) -> (TlsStream, BufDnsStreamHandle) { let (message_sender, outbound_messages) = BufDnsStreamHandle::new(peer_addr); let stream = TcpStream::from_stream_with_receiver( AsyncIoTokioAsStd(stream), peer_addr, outbound_messages, ); (stream, message_sender) } /// A builder for the TlsStream #[derive(Default)] pub struct TlsStreamBuilder { ca_chain: Vec, identity: Option, bind_addr: Option, marker: PhantomData, } impl TlsStreamBuilder { /// Constructs a new TlsStreamBuilder pub fn new() -> Self { Self { ca_chain: vec![], identity: None, bind_addr: None, marker: PhantomData, } } /// Add a custom trusted peer certificate or certificate authority. /// /// If this is the 'client' then the 'server' must have it associated as it's `identity`, or have had the `identity` signed by this certificate. pub fn add_ca(&mut self, ca: Certificate) { self.ca_chain.push(ca); } /// Client side identity for client auth in TLS (aka mutual TLS auth) #[cfg(feature = "mtls")] pub fn identity(&mut self, identity: Identity) { self.identity = Some(identity); } /// Sets the address to connect from. pub fn bind_addr(&mut self, bind_addr: SocketAddr) { self.bind_addr = Some(bind_addr); } /// Similar to `build`, but with customized stream future. /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Public Key Info (SPKI) name, as associated to a certificate #[allow(clippy::type_complexity)] pub fn build_with_future( self, future: F, name_server: SocketAddr, dns_name: String, ) -> ( // TODO: change to impl? Pin, io::Error>> + Send>>, BufDnsStreamHandle, ) where S: DnsTcpStream, F: Future> + Send + Unpin + 'static, { let (message_sender, outbound_messages) = BufDnsStreamHandle::new(name_server); let stream = self.inner_build(future, name_server, dns_name, outbound_messages); (Box::pin(stream), message_sender) } async fn inner_build( self, future: F, name_server: SocketAddr, dns_name: String, outbound_messages: StreamReceiver, ) -> Result, io::Error> where F: Future> + Send + Unpin + 'static, { use crate::native_tls::tls_stream; let tcp_stream = future.await; let ca_chain = self.ca_chain.clone(); let identity = self.identity; // TODO: for some reason the above wouldn't accept a ? let tcp_stream = match tcp_stream { Ok(tcp_stream) => AsyncIoStdAsTokio(tcp_stream), Err(err) => return Err(err), }; // This set of futures collapses the next tcp socket into a stream which can be used for // sending and receiving tcp packets. let tls_connector = tls_stream::tls_new(ca_chain, identity) .map(TokioTlsConnector::from) .map_err(|e| { io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}")) })?; let tls_connected = tls_connector .connect(&dns_name, tcp_stream) .map_err(|e| { io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}")) }) .await?; Ok(TcpStream::from_stream_with_receiver( AsyncIoTokioAsStd(tls_connected), name_server, outbound_messages, )) } } impl TlsStreamBuilder { /// Creates a new TlsStream to the specified name_server /// /// [RFC 7858](https://tools.ietf.org/html/rfc7858), DNS over TLS, May 2016 /// /// ```text /// 3.2. TLS Handshake and Authentication /// /// Once the DNS client succeeds in connecting via TCP on the well-known /// port for DNS over TLS, it proceeds with the TLS handshake [RFC5246], /// following the best practices specified in [BCP195]. /// /// The client will then authenticate the server, if required. This /// document does not propose new ideas for authentication. Depending on /// the privacy profile in use (Section 4), the DNS client may choose not /// to require authentication of the server, or it may make use of a /// trusted Subject Public Key Info (SPKI) Fingerprint pin set. /// /// After TLS negotiation completes, the connection will be encrypted and /// is now protected from eavesdropping. /// ``` /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Public Key Info (SPKI) name, as associated to a certificate #[allow(clippy::type_complexity)] pub fn build( self, name_server: SocketAddr, dns_name: String, ) -> ( // TODO: change to impl? Pin, io::Error>> + Send>>, BufDnsStreamHandle, ) { let (message_sender, outbound_messages) = BufDnsStreamHandle::new(name_server); let conn = S::connect_with_bind(name_server, self.bind_addr); let stream = self.inner_build(conn, name_server, dns_name, outbound_messages); (Box::pin(stream), message_sender) } } hickory-proto-0.24.0/src/op/edns.rs000064400000000000000000000175321046102023000152340ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Extended DNS options use std::fmt; use crate::{ error::*, rr::{ rdata::{ opt::{EdnsCode, EdnsOption}, OPT, }, DNSClass, Name, RData, Record, RecordType, }, serialize::binary::{BinEncodable, BinEncoder}, }; /// Edns implements the higher level concepts for working with extended dns as it is used to create or be /// created from OPT record data. #[derive(Debug, PartialEq, Eq, Clone)] pub struct Edns { // high 8 bits that make up the 12 bit total field when included with the 4bit rcode from the // header (from TTL) rcode_high: u8, // Indicates the implementation level of the setter. (from TTL) version: u8, // Is DNSSEC supported (from TTL) dnssec_ok: bool, // max payload size, minimum of 512, (from RR CLASS) max_payload: u16, options: OPT, } impl Default for Edns { fn default() -> Self { Self { rcode_high: 0, version: 0, dnssec_ok: false, max_payload: 512, options: OPT::default(), } } } impl Edns { /// Creates a new extended DNS object. pub fn new() -> Self { Self::default() } /// The high order bytes for the response code in the DNS Message pub fn rcode_high(&self) -> u8 { self.rcode_high } /// Returns the EDNS version pub fn version(&self) -> u8 { self.version } /// Specifies that DNSSEC is supported for this Client or Server pub fn dnssec_ok(&self) -> bool { self.dnssec_ok } /// Maximum supported size of the DNS payload pub fn max_payload(&self) -> u16 { self.max_payload } /// Returns the Option associated with the code pub fn option(&self, code: EdnsCode) -> Option<&EdnsOption> { self.options.get(code) } /// Returns the options portion of EDNS pub fn options(&self) -> &OPT { &self.options } /// Returns a mutable options portion of EDNS pub fn options_mut(&mut self) -> &mut OPT { &mut self.options } /// Set the high order bits for the result code. pub fn set_rcode_high(&mut self, rcode_high: u8) -> &mut Self { self.rcode_high = rcode_high; self } /// Set the EDNS version pub fn set_version(&mut self, version: u8) -> &mut Self { self.version = version; self } /// Set to true if DNSSEC is supported pub fn set_dnssec_ok(&mut self, dnssec_ok: bool) -> &mut Self { self.dnssec_ok = dnssec_ok; self } /// Set the maximum payload which can be supported /// From RFC 6891: `Values lower than 512 MUST be treated as equal to 512` pub fn set_max_payload(&mut self, max_payload: u16) -> &mut Self { self.max_payload = max_payload.max(512); self } /// Set the specified EDNS option #[deprecated(note = "Please use options_mut().insert() to modify")] pub fn set_option(&mut self, option: EdnsOption) { self.options.insert(option); } } // FIXME: this should be a TryFrom impl<'a> From<&'a Record> for Edns { fn from(value: &'a Record) -> Self { assert!(value.record_type() == RecordType::OPT); let rcode_high: u8 = ((value.ttl() & 0xFF00_0000u32) >> 24) as u8; let version: u8 = ((value.ttl() & 0x00FF_0000u32) >> 16) as u8; let dnssec_ok: bool = value.ttl() & 0x0000_8000 == 0x0000_8000; let max_payload: u16 = u16::from(value.dns_class()); let options: OPT = match value.data() { Some(RData::NULL(..)) | None => { // NULL, there was no data in the OPT OPT::default() } Some(RData::OPT(ref option_data)) => { option_data.clone() // TODO: Edns should just refer to this, have the same lifetime as the Record } _ => { // this should be a coding error, as opposed to a parsing error. panic!("rr_type doesn't match the RData: {:?}", value.data()) // valid panic, never should happen } }; Self { rcode_high, version, dnssec_ok, max_payload, options, } } } impl<'a> From<&'a Edns> for Record { /// This returns a Resource Record that is formatted for Edns(0). /// Note: the rcode_high value is only part of the rcode, the rest is part of the base fn from(value: &'a Edns) -> Self { let mut record = Self::new(); record.set_name(Name::root()); record.set_rr_type(RecordType::OPT); record.set_dns_class(DNSClass::for_opt(value.max_payload())); // rebuild the TTL field let mut ttl: u32 = u32::from(value.rcode_high()) << 24; ttl |= u32::from(value.version()) << 16; if value.dnssec_ok() { ttl |= 0x0000_8000; } record.set_ttl(ttl); // now for each option, write out the option array // also, since this is a hash, there is no guarantee that ordering will be preserved from // the original binary format. // maybe switch to: https://crates.io/crates/linked-hash-map/ record.set_data(Some(RData::OPT(value.options().clone()))); record } } impl BinEncodable for Edns { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.emit(0)?; // Name::root RecordType::OPT.emit(encoder)?; //self.rr_type.emit(encoder)?; DNSClass::for_opt(self.max_payload()).emit(encoder)?; // self.dns_class.emit(encoder)?; // rebuild the TTL field let mut ttl: u32 = u32::from(self.rcode_high()) << 24; ttl |= u32::from(self.version()) << 16; if self.dnssec_ok() { ttl |= 0x0000_8000; } encoder.emit_u32(ttl)?; // write the opts as rdata... let place = encoder.place::()?; self.options.emit(encoder)?; let len = encoder.len_since_place(&place); assert!(len <= u16::max_value() as usize); place.replace(encoder, len as u16)?; Ok(()) } } impl fmt::Display for Edns { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let version = self.version; let dnssec_ok = self.dnssec_ok; let max_payload = self.max_payload; write!( f, "version: {version} dnssec_ok: {dnssec_ok} max_payload: {max_payload} opts: {opts_len}", version = version, dnssec_ok = dnssec_ok, max_payload = max_payload, opts_len = self.options().as_ref().len() ) } } #[cfg(feature = "dnssec")] #[test] fn test_encode_decode() { use crate::rr::dnssec::SupportedAlgorithms; let mut edns: Edns = Edns::new(); edns.set_dnssec_ok(true); edns.set_max_payload(0x8008); edns.set_version(0x40); edns.set_rcode_high(0x01); edns.options_mut() .insert(EdnsOption::DAU(SupportedAlgorithms::all())); let record: Record = (&edns).into(); let edns_decode: Edns = (&record).into(); assert_eq!(edns.dnssec_ok(), edns_decode.dnssec_ok()); assert_eq!(edns.max_payload(), edns_decode.max_payload()); assert_eq!(edns.version(), edns_decode.version()); assert_eq!(edns.rcode_high(), edns_decode.rcode_high()); assert_eq!(edns.options(), edns_decode.options()); // re-insert and remove using mut edns.options_mut() .insert(EdnsOption::DAU(SupportedAlgorithms::all())); edns.options_mut().remove(EdnsCode::DAU); assert!(edns.option(EdnsCode::DAU).is_none()); } hickory-proto-0.24.0/src/op/header.rs000064400000000000000000000566571046102023000155460ustar 00000000000000// Copyright 2015-2021 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Message metadata use std::{convert::From, fmt}; use crate::{ error::*, op::{op_code::OpCode, response_code::ResponseCode}, serialize::binary::*, }; /// Metadata for the `Message` struct. /// /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035) /// /// ```text /// 4.1.1. Header section format /// /// The header contains the following fields /// /// 1 1 1 1 1 1 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// | ID | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// |QR| Opcode |AA|TC|RD|RA|ZZ|AD|CD| RCODE | /// AD and CD from RFC4035 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// | QDCOUNT / ZCOUNT | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// | ANCOUNT / PRCOUNT | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// | NSCOUNT / UPCOUNT | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// | ARCOUNT / ADCOUNT | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// /// where /// /// Z Reserved for future use. Must be zero in all queries /// and responses. /// /// ``` /// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Hash)] pub struct Header { id: u16, message_type: MessageType, op_code: OpCode, authoritative: bool, truncation: bool, recursion_desired: bool, recursion_available: bool, authentic_data: bool, checking_disabled: bool, response_code: ResponseCode, query_count: u16, answer_count: u16, name_server_count: u16, additional_count: u16, } impl fmt::Display for Header { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, "{id}:{message_type}:{flags}:{code:?}:{op_code}:{answers}/{authorities}/{additionals}", id = self.id, message_type = self.message_type, flags = self.flags(), code = self.response_code, op_code = self.op_code, answers = self.answer_count, authorities = self.name_server_count, additionals = self.additional_count, ) } } /// Message types are either Query (also Update) or Response #[derive(Debug, PartialEq, Eq, PartialOrd, Copy, Clone, Hash)] pub enum MessageType { /// Queries are Client requests, these are either Queries or Updates Query, /// Response message from the Server or upstream Resolver Response, } impl fmt::Display for MessageType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let s = match self { Self::Query => "QUERY", Self::Response => "RESPONSE", }; f.write_str(s) } } /// All the flags of the request/response header #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct Flags { authoritative: bool, truncation: bool, recursion_desired: bool, recursion_available: bool, authentic_data: bool, checking_disabled: bool, } /// We are following the `dig` commands display format for the header flags /// /// Example: "RD,AA,RA;" is Recursion-Desired, Authoritative-Answer, Recursion-Available. impl fmt::Display for Flags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { const SEPARATOR: &str = ","; let flags = [ (self.recursion_desired, "RD"), (self.checking_disabled, "CD"), (self.truncation, "TC"), (self.authoritative, "AA"), (self.recursion_available, "RA"), (self.authentic_data, "AD"), ]; let mut iter = flags .iter() .cloned() .filter_map(|(flag, s)| if flag { Some(s) } else { None }); // print first without a separator, then print the rest. if let Some(s) = iter.next() { f.write_str(s)? } for s in iter { f.write_str(SEPARATOR)?; f.write_str(s)?; } Ok(()) } } impl Default for Header { fn default() -> Self { Self::new() } } impl Header { // TODO: we should make id, message_type and op_code all required and non-editable /// A default Header, not very useful. pub const fn new() -> Self { Self { id: 0, message_type: MessageType::Query, op_code: OpCode::Query, authoritative: false, truncation: false, recursion_desired: false, recursion_available: false, authentic_data: false, checking_disabled: false, response_code: ResponseCode::NoError, query_count: 0, answer_count: 0, name_server_count: 0, additional_count: 0, } } /// Construct a new header based off the request header. This copies over the RD (recursion-desired) /// and CD (checking-disabled), as well as the op_code and id of the request. /// /// See /// /// ```text /// The AA, TC, RD, RA, and CD bits are each theoretically meaningful /// only in queries or only in responses, depending on the bit. The AD /// bit was only meaningful in responses but is expected to have a /// separate but related meaning in queries (see Section 5.7 of /// [RFC6840]). Only the RD and CD bits are expected to be copied from /// the query to the response; however, some DNS implementations copy all /// the query header as the initial value of the response header. Thus, /// any attempt to use a "query" bit with a different meaning in a /// response or to define a query meaning for a "response" bit may be /// dangerous, given the existing implementation. Meanings for these /// bits may only be assigned by a Standards Action. /// ``` pub fn response_from_request(header: &Self) -> Self { Self { id: header.id, message_type: MessageType::Response, op_code: header.op_code, authoritative: false, truncation: false, recursion_desired: header.recursion_desired, recursion_available: false, authentic_data: false, checking_disabled: header.checking_disabled, response_code: ResponseCode::default(), query_count: 0, answer_count: 0, name_server_count: 0, additional_count: 0, } } /// Length of the header, always 12 bytes #[inline(always)] pub fn len() -> usize { 12 /* this is always 12 bytes */ } /// Sets the id of the message, for queries this should be random. pub fn set_id(&mut self, id: u16) -> &mut Self { self.id = id; self } /// Sets the message type, Queries and Updates both use Query. pub fn set_message_type(&mut self, message_type: MessageType) -> &mut Self { self.message_type = message_type; self } /// Set the operation code for the message pub fn set_op_code(&mut self, op_code: OpCode) -> &mut Self { self.op_code = op_code; self } /// From the server is specifies that it is an authoritative response. pub fn set_authoritative(&mut self, authoritative: bool) -> &mut Self { self.authoritative = authoritative; self } /// Specifies that the records were too large for the payload. /// /// See EDNS or TCP for resolutions to truncation. pub fn set_truncated(&mut self, truncated: bool) -> &mut Self { self.truncation = truncated; self } /// Specify that the resolver should recursively request data from upstream DNS nodes pub fn set_recursion_desired(&mut self, recursion_desired: bool) -> &mut Self { self.recursion_desired = recursion_desired; self } /// Specifies that recursion is available from this or the remote resolver pub fn set_recursion_available(&mut self, recursion_available: bool) -> &mut Self { self.recursion_available = recursion_available; self } /// Specifies that the data is authentic, i.e. the resolver believes all data to be valid through DNSSEC pub fn set_authentic_data(&mut self, authentic_data: bool) -> &mut Self { self.authentic_data = authentic_data; self } /// Used during recursive resolution to specified if a resolver should or should not validate DNSSEC signatures pub fn set_checking_disabled(&mut self, checking_disabled: bool) -> &mut Self { self.checking_disabled = checking_disabled; self } /// A method to get all header flags (useful for Display purposes) pub fn flags(&self) -> Flags { Flags { authoritative: self.authoritative, authentic_data: self.authentic_data, checking_disabled: self.checking_disabled, recursion_available: self.recursion_available, recursion_desired: self.recursion_desired, truncation: self.truncation, } } /// The low response code (original response codes before EDNS extensions) pub fn set_response_code(&mut self, response_code: ResponseCode) -> &mut Self { self.response_code = response_code; self } /// This combines the high and low response code values to form the complete ResponseCode from the EDNS record. /// The existing high order bits will be overwritten (if set), and `high_response_code` will be merge with /// the existing low order bits. /// /// This is intended for use during decoding. #[doc(hidden)] pub fn merge_response_code(&mut self, high_response_code: u8) { self.response_code = ResponseCode::from(high_response_code, self.response_code.low()); } /// Number or query records in the message pub fn set_query_count(&mut self, query_count: u16) -> &mut Self { self.query_count = query_count; self } /// Number of answer records in the message pub fn set_answer_count(&mut self, answer_count: u16) -> &mut Self { self.answer_count = answer_count; self } /// Number of name server records in the message pub fn set_name_server_count(&mut self, name_server_count: u16) -> &mut Self { self.name_server_count = name_server_count; self } /// Number of additional records in the message pub fn set_additional_count(&mut self, additional_count: u16) -> &mut Self { self.additional_count = additional_count; self } /// ```text /// ID A 16 bit identifier assigned by the program that /// generates any kind of query. This identifier is copied /// the corresponding reply and can be used by the requester /// to match up replies to outstanding queries. /// ``` pub fn id(&self) -> u16 { self.id } /// ```text /// QR A one bit field that specifies whether this message is a /// query (0), or a response (1). /// ``` pub fn message_type(&self) -> MessageType { self.message_type } /// ```text /// OPCODE A four bit field that specifies kind of query in this /// message. This value is set by the originator of a query /// and copied into the response. The values are: /// ``` pub fn op_code(&self) -> OpCode { self.op_code } /// ```text /// AA Authoritative Answer - this bit is valid in responses, /// and specifies that the responding name server is an /// authority for the domain name in question section. /// /// Note that the contents of the answer section may have /// multiple owner names because of aliases. The AA bit /// corresponds to the name which matches the query name, or /// the first owner name in the answer section. /// ``` pub fn authoritative(&self) -> bool { self.authoritative } /// ```text /// TC TrunCation - specifies that this message was truncated /// due to length greater than that permitted on the /// transmission channel. /// ``` pub fn truncated(&self) -> bool { self.truncation } /// ```text /// RD Recursion Desired - this bit may be set in a query and /// is copied into the response. If RD is set, it directs /// the name server to pursue the query recursively. /// Recursive query support is optional. /// ``` pub fn recursion_desired(&self) -> bool { self.recursion_desired } /// ```text /// RA Recursion Available - this be is set or cleared in a /// response, and denotes whether recursive query support is /// available in the name server. /// ``` pub fn recursion_available(&self) -> bool { self.recursion_available } /// [RFC 4035, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4035#section-3.1.6) /// /// ```text /// /// 3.1.6. The AD and CD Bits in an Authoritative Response /// /// The CD and AD bits are designed for use in communication between /// security-aware resolvers and security-aware recursive name servers. /// These bits are for the most part not relevant to query processing by /// security-aware authoritative name servers. /// /// A security-aware name server does not perform signature validation /// for authoritative data during query processing, even when the CD bit /// is clear. A security-aware name server SHOULD clear the CD bit when /// composing an authoritative response. /// /// A security-aware name server MUST NOT set the AD bit in a response /// unless the name server considers all RRsets in the Answer and /// Authority sections of the response to be authentic. A security-aware /// name server's local policy MAY consider data from an authoritative /// zone to be authentic without further validation. However, the name /// server MUST NOT do so unless the name server obtained the /// authoritative zone via secure means (such as a secure zone transfer /// mechanism) and MUST NOT do so unless this behavior has been /// configured explicitly. /// /// A security-aware name server that supports recursion MUST follow the /// rules for the CD and AD bits given in Section 3.2 when generating a /// response that involves data obtained via recursion. /// ``` pub fn authentic_data(&self) -> bool { self.authentic_data } /// see `is_authentic_data()` pub fn checking_disabled(&self) -> bool { self.checking_disabled } /// ```text /// RCODE Response code - this 4 bit field is set as part of /// responses. The values have the following /// interpretation: /// ``` pub fn response_code(&self) -> ResponseCode { self.response_code } /// ```text /// QDCOUNT an unsigned 16 bit integer specifying the number of /// entries in the question section. /// ``` /// /// # Return value /// /// If this is a query, this will return the number of queries in the query section of the // message, fo updates this represents the zone count (must be no more than 1). pub fn query_count(&self) -> u16 { self.query_count } /// ```text /// ANCOUNT an unsigned 16 bit integer specifying the number of /// resource records in the answer section. /// ``` /// /// # Return value /// /// For query responses this is the number of records in the answer section, should be 0 for /// requests, for updates this is the count of prerequisite records. pub fn answer_count(&self) -> u16 { self.answer_count } /// for queries this is the nameservers which are authorities for the SOA of the Record /// for updates this is the update record count /// ```text /// NSCOUNT an unsigned 16 bit integer specifying the number of name /// server resource records in the authority records /// section. /// ``` /// /// # Return value /// /// For query responses this is the number of authorities, or nameservers, in the name server /// section, for updates this is the number of update records being sent. pub fn name_server_count(&self) -> u16 { self.name_server_count } /// ```text /// ARCOUNT an unsigned 16 bit integer specifying the number of /// resource records in the additional records section. /// ``` /// /// # Return value /// /// This is the additional record section count, this section may include EDNS options. pub fn additional_count(&self) -> u16 { self.additional_count } } impl BinEncodable for Header { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.reserve(12)?; // the 12 bytes for the following fields; // Id encoder.emit_u16(self.id)?; // IsQuery, OpCode, Authoritative, Truncation, RecursionDesired let mut q_opcd_a_t_r: u8 = if let MessageType::Response = self.message_type { 0x80 } else { 0x00 }; q_opcd_a_t_r |= u8::from(self.op_code) << 3; q_opcd_a_t_r |= if self.authoritative { 0x4 } else { 0x0 }; q_opcd_a_t_r |= if self.truncation { 0x2 } else { 0x0 }; q_opcd_a_t_r |= if self.recursion_desired { 0x1 } else { 0x0 }; encoder.emit(q_opcd_a_t_r)?; // IsRecursionAvailable, Triple 0's, ResponseCode let mut r_z_ad_cd_rcod: u8 = if self.recursion_available { 0b1000_0000 } else { 0b0000_0000 }; r_z_ad_cd_rcod |= if self.authentic_data { 0b0010_0000 } else { 0b0000_0000 }; r_z_ad_cd_rcod |= if self.checking_disabled { 0b0001_0000 } else { 0b0000_0000 }; r_z_ad_cd_rcod |= self.response_code.low(); encoder.emit(r_z_ad_cd_rcod)?; encoder.emit_u16(self.query_count)?; encoder.emit_u16(self.answer_count)?; encoder.emit_u16(self.name_server_count)?; encoder.emit_u16(self.additional_count)?; Ok(()) } } impl<'r> BinDecodable<'r> for Header { fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult { let id = decoder.read_u16()?.unverified(/*it is valid for this to be any u16*/); let q_opcd_a_t_r = decoder.pop()?.unverified(/*used as a bitfield, this is safe*/); // if the first bit is set let message_type = if (0b1000_0000 & q_opcd_a_t_r) == 0b1000_0000 { MessageType::Response } else { MessageType::Query }; // the 4bit opcode, masked and then shifted right 3bits for the u8... let op_code: OpCode = OpCode::from_u8((0b0111_1000 & q_opcd_a_t_r) >> 3)?; let authoritative = (0b0000_0100 & q_opcd_a_t_r) == 0b0000_0100; let truncation = (0b0000_0010 & q_opcd_a_t_r) == 0b0000_0010; let recursion_desired = (0b0000_0001 & q_opcd_a_t_r) == 0b0000_0001; let r_z_ad_cd_rcod = decoder.pop()?.unverified(/*used as a bitfield, this is safe*/); // fail fast... let recursion_available = (0b1000_0000 & r_z_ad_cd_rcod) == 0b1000_0000; let authentic_data = (0b0010_0000 & r_z_ad_cd_rcod) == 0b0010_0000; let checking_disabled = (0b0001_0000 & r_z_ad_cd_rcod) == 0b0001_0000; let response_code: u8 = 0b0000_1111 & r_z_ad_cd_rcod; let response_code = ResponseCode::from_low(response_code); // TODO: We should pass these restrictions on, they can't be trusted, but that would seriously complicate the Header type.. // TODO: perhaps the read methods for BinDecodable should return Restrict? let query_count = decoder.read_u16()?.unverified(/*this must be verified when reading queries*/); let answer_count = decoder.read_u16()?.unverified(/*this must be evaluated when reading records*/); let name_server_count = decoder.read_u16()?.unverified(/*this must be evaluated when reading records*/); let additional_count = decoder.read_u16()?.unverified(/*this must be evaluated when reading records*/); // TODO: question, should this use the builder pattern instead? might be cleaner code, but // this guarantees that the Header is fully instantiated with all values... Ok(Self { id, message_type, op_code, authoritative, truncation, recursion_desired, recursion_available, authentic_data, checking_disabled, response_code, query_count, answer_count, name_server_count, additional_count, }) } } #[test] fn test_parse() { let byte_vec = vec![ 0x01, 0x10, 0xAA, 0x83, // 0b1010 1010 1000 0011 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, ]; let mut decoder = BinDecoder::new(&byte_vec); let expect = Header { id: 0x0110, message_type: MessageType::Response, op_code: OpCode::Update, authoritative: false, truncation: true, recursion_desired: false, recursion_available: true, authentic_data: false, checking_disabled: false, response_code: ResponseCode::NXDomain, query_count: 0x8877, answer_count: 0x6655, name_server_count: 0x4433, additional_count: 0x2211, }; let got = Header::read(&mut decoder).unwrap(); assert_eq!(got, expect); } #[test] fn test_write() { let header = Header { id: 0x0110, message_type: MessageType::Response, op_code: OpCode::Update, authoritative: false, truncation: true, recursion_desired: false, recursion_available: true, authentic_data: false, checking_disabled: false, response_code: ResponseCode::NXDomain, query_count: 0x8877, answer_count: 0x6655, name_server_count: 0x4433, additional_count: 0x2211, }; let expect: Vec = vec![ 0x01, 0x10, 0xAA, 0x83, // 0b1010 1010 1000 0011 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, ]; let mut bytes = Vec::with_capacity(512); { let mut encoder = BinEncoder::new(&mut bytes); header.emit(&mut encoder).unwrap(); } assert_eq!(bytes, expect); } hickory-proto-0.24.0/src/op/lower_query.rs000064400000000000000000000060001046102023000166440ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::fmt::{self, Display}; use crate::error::*; use crate::op::Query; use crate::rr::LowerName; use crate::rr::{DNSClass, RecordType}; use crate::serialize::binary::*; /// Identical to [crate::op::Query], except that the Name is guaranteed to be in lower case form #[derive(Clone, Debug, PartialEq, Eq)] pub struct LowerQuery { name: LowerName, original: Query, } impl LowerQuery { /// Create a new query from name and type, class defaults to IN pub fn query(query: Query) -> Self { Self { name: LowerName::new(query.name()), original: query, } } /// ```text /// QNAME a domain name represented as a sequence of labels, where /// each label consists of a length octet followed by that /// number of octets. The domain name terminates with the /// zero length octet for the null label of the root. Note /// that this field may be an odd number of octets; no /// padding is used. /// ``` pub fn name(&self) -> &LowerName { &self.name } /// Returns the original with the `Name`s case preserved pub fn original(&self) -> &Query { &self.original } /// ```text /// QTYPE a two octet code which specifies the type of the query. /// The values for this field include all codes valid for a /// TYPE field, together with some more general codes which /// can match more than one type of RR. /// ``` pub fn query_type(&self) -> RecordType { self.original.query_type() } /// ```text /// QCLASS a two octet code that specifies the class of the query. /// For example, the QCLASS field is IN for the Internet. /// ``` pub fn query_class(&self) -> DNSClass { self.original.query_class() } } impl From for LowerQuery { fn from(query: Query) -> Self { Self::query(query) } } impl BinEncodable for LowerQuery { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { self.original.emit(encoder) } } impl<'r> BinDecodable<'r> for LowerQuery { fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult { let original = Query::read(decoder)?; Ok(Self::query(original)) } } impl Display for LowerQuery { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, "name: {} type: {} class: {}", self.name, self.original.query_type(), self.original.query_class() ) } } hickory-proto-0.24.0/src/op/message.rs000064400000000000000000001216571046102023000157330ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Basic protocol message for DNS use std::{fmt, iter, mem, ops::Deref, sync::Arc}; use tracing::{debug, warn}; use crate::{ error::*, op::{Edns, Header, MessageType, OpCode, Query, ResponseCode}, rr::{Record, RecordType}, serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder, EncodeMode}, xfer::DnsResponse, }; /// The basic request and response data structure, used for all DNS protocols. /// /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035) /// /// ```text /// 4.1. Format /// /// All communications inside of the domain protocol are carried in a single /// format called a message. The top level format of message is divided /// into 5 sections (some of which are empty in certain cases) shown below: /// /// +--------------------------+ /// | Header | /// +--------------------------+ /// | Question / Zone | the question for the name server /// +--------------------------+ /// | Answer / Prerequisite | RRs answering the question /// +--------------------------+ /// | Authority / Update | RRs pointing toward an authority /// +--------------------------+ /// | Additional | RRs holding additional information /// +--------------------------+ /// /// The header section is always present. The header includes fields that /// specify which of the remaining sections are present, and also specify /// whether the message is a query or a response, a standard query or some /// other opcode, etc. /// /// The names of the sections after the header are derived from their use in /// standard queries. The question section contains fields that describe a /// question to a name server. These fields are a query type (QTYPE), a /// query class (QCLASS), and a query domain name (QNAME). The last three /// sections have the same format: a possibly empty list of concatenated /// resource records (RRs). The answer section contains RRs that answer the /// question; the authority section contains RRs that point toward an /// authoritative name server; the additional records section contains RRs /// which relate to the query, but are not strictly answers for the /// question. /// ``` /// /// By default Message is a Query. Use the Message::as_update() to create and update, or /// Message::new_update() #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct Message { header: Header, queries: Vec, answers: Vec, name_servers: Vec, additionals: Vec, signature: Vec, edns: Option, } /// Returns a new Header with accurate counts for each Message section pub fn update_header_counts( current_header: &Header, is_truncated: bool, counts: HeaderCounts, ) -> Header { assert!(counts.query_count <= u16::max_value() as usize); assert!(counts.answer_count <= u16::max_value() as usize); assert!(counts.nameserver_count <= u16::max_value() as usize); assert!(counts.additional_count <= u16::max_value() as usize); // TODO: should the function just take by value? let mut header = *current_header; header .set_query_count(counts.query_count as u16) .set_answer_count(counts.answer_count as u16) .set_name_server_count(counts.nameserver_count as u16) .set_additional_count(counts.additional_count as u16) .set_truncated(is_truncated); header } /// Tracks the counts of the records in the Message. /// /// This is only used internally during serialization. #[derive(Clone, Copy, Debug)] pub struct HeaderCounts { /// The number of queries in the Message pub query_count: usize, /// The number of answers in the Message pub answer_count: usize, /// The number of nameservers or authorities in the Message pub nameserver_count: usize, /// The number of additional records in the Message pub additional_count: usize, } impl Message { /// Returns a new "empty" Message pub fn new() -> Self { Self { header: Header::new(), queries: Vec::new(), answers: Vec::new(), name_servers: Vec::new(), additionals: Vec::new(), signature: Vec::new(), edns: None, } } /// Returns a Message constructed with error details to return to a client /// /// # Arguments /// /// * `id` - message id should match the request message id /// * `op_code` - operation of the request /// * `response_code` - the error code for the response pub fn error_msg(id: u16, op_code: OpCode, response_code: ResponseCode) -> Self { let mut message = Self::new(); message .set_message_type(MessageType::Response) .set_id(id) .set_response_code(response_code) .set_op_code(op_code); message } /// Truncates a Message, this blindly removes all response fields and sets truncated to `true` pub fn truncate(&self) -> Self { let mut truncated = self.clone(); truncated.set_truncated(true); // drops additional/answer/queries so len is 0 truncated.take_additionals(); truncated.take_answers(); truncated.take_queries(); // TODO, perhaps just quickly add a few response records here? that we know would fit? truncated } /// Sets the `Header` with provided pub fn set_header(&mut self, header: Header) -> &mut Self { self.header = header; self } /// see `Header::set_id` pub fn set_id(&mut self, id: u16) -> &mut Self { self.header.set_id(id); self } /// see `Header::set_message_type` pub fn set_message_type(&mut self, message_type: MessageType) -> &mut Self { self.header.set_message_type(message_type); self } /// see `Header::set_op_code` pub fn set_op_code(&mut self, op_code: OpCode) -> &mut Self { self.header.set_op_code(op_code); self } /// see `Header::set_authoritative` pub fn set_authoritative(&mut self, authoritative: bool) -> &mut Self { self.header.set_authoritative(authoritative); self } /// see `Header::set_truncated` pub fn set_truncated(&mut self, truncated: bool) -> &mut Self { self.header.set_truncated(truncated); self } /// see `Header::set_recursion_desired` pub fn set_recursion_desired(&mut self, recursion_desired: bool) -> &mut Self { self.header.set_recursion_desired(recursion_desired); self } /// see `Header::set_recursion_available` pub fn set_recursion_available(&mut self, recursion_available: bool) -> &mut Self { self.header.set_recursion_available(recursion_available); self } /// see `Header::set_authentic_data` pub fn set_authentic_data(&mut self, authentic_data: bool) -> &mut Self { self.header.set_authentic_data(authentic_data); self } /// see `Header::set_checking_disabled` pub fn set_checking_disabled(&mut self, checking_disabled: bool) -> &mut Self { self.header.set_checking_disabled(checking_disabled); self } /// see `Header::set_response_code` pub fn set_response_code(&mut self, response_code: ResponseCode) -> &mut Self { self.header.set_response_code(response_code); self } /// Add a query to the Message, either the query response from the server, or the request Query. pub fn add_query(&mut self, query: Query) -> &mut Self { self.queries.push(query); self } /// Adds an iterator over a set of Queries to be added to the message pub fn add_queries(&mut self, queries: Q) -> &mut Self where Q: IntoIterator, I: Iterator, { for query in queries { self.add_query(query); } self } /// Add an answer to the Message pub fn add_answer(&mut self, record: Record) -> &mut Self { self.answers.push(record); self } /// Add all the records from the iterator to the answers section of the Message pub fn add_answers(&mut self, records: R) -> &mut Self where R: IntoIterator, I: Iterator, { for record in records { self.add_answer(record); } self } /// Sets the answers to the specified set of Records. /// /// # Panics /// /// Will panic if answer records are already associated to the message. pub fn insert_answers(&mut self, records: Vec) { assert!(self.answers.is_empty()); self.answers = records; } /// Add a name server record to the Message pub fn add_name_server(&mut self, record: Record) -> &mut Self { self.name_servers.push(record); self } /// Add all the records in the Iterator to the name server section of the message pub fn add_name_servers(&mut self, records: R) -> &mut Self where R: IntoIterator, I: Iterator, { for record in records { self.add_name_server(record); } self } /// Sets the name_servers to the specified set of Records. /// /// # Panics /// /// Will panic if name_servers records are already associated to the message. pub fn insert_name_servers(&mut self, records: Vec) { assert!(self.name_servers.is_empty()); self.name_servers = records; } /// Add an additional Record to the message pub fn add_additional(&mut self, record: Record) -> &mut Self { self.additionals.push(record); self } /// Add all the records from the iterator to the additionals section of the Message pub fn add_additionals(&mut self, records: R) -> &mut Self where R: IntoIterator, I: Iterator, { for record in records { self.add_additional(record); } self } /// Sets the additional to the specified set of Records. /// /// # Panics /// /// Will panic if additional records are already associated to the message. pub fn insert_additionals(&mut self, records: Vec) { assert!(self.additionals.is_empty()); self.additionals = records; } /// Add the EDNS section to the Message pub fn set_edns(&mut self, edns: Edns) -> &mut Self { self.edns = Some(edns); self } /// Add a SIG0 record, i.e. sign this message /// /// This must be used only after all records have been associated. Generally this will be handled by the client and not need to be used directly #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub fn add_sig0(&mut self, record: Record) -> &mut Self { assert_eq!(RecordType::SIG, record.record_type()); self.signature.push(record); self } /// Add a TSIG record, i.e. authenticate this message /// /// This must be used only after all records have been associated. Generally this will be handled by the client and not need to be used directly #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub fn add_tsig(&mut self, record: Record) -> &mut Self { assert_eq!(RecordType::TSIG, record.record_type()); self.signature.push(record); self } /// Gets the header of the Message pub fn header(&self) -> &Header { &self.header } /// see `Header::id()` pub fn id(&self) -> u16 { self.header.id() } /// see `Header::message_type()` pub fn message_type(&self) -> MessageType { self.header.message_type() } /// see `Header::op_code()` pub fn op_code(&self) -> OpCode { self.header.op_code() } /// see `Header::authoritative()` pub fn authoritative(&self) -> bool { self.header.authoritative() } /// see `Header::truncated()` pub fn truncated(&self) -> bool { self.header.truncated() } /// see `Header::recursion_desired()` pub fn recursion_desired(&self) -> bool { self.header.recursion_desired() } /// see `Header::recursion_available()` pub fn recursion_available(&self) -> bool { self.header.recursion_available() } /// see `Header::authentic_data()` pub fn authentic_data(&self) -> bool { self.header.authentic_data() } /// see `Header::checking_disabled()` pub fn checking_disabled(&self) -> bool { self.header.checking_disabled() } /// # Return value /// /// The `ResponseCode`, if this is an EDNS message then this will join the section from the OPT /// record to create the EDNS `ResponseCode` pub fn response_code(&self) -> ResponseCode { self.header.response_code() } /// Returns the query from this Message. /// /// In almost all cases, a Message will only contain one query. This is a convenience function to get the single query. /// See the alternative `queries*` methods for the raw set of queries in the Message pub fn query(&self) -> Option<&Query> { self.queries.first() } /// ```text /// Question Carries the query name and other query parameters. /// ``` pub fn queries(&self) -> &[Query] { &self.queries } /// Provides mutable access to `queries` pub fn queries_mut(&mut self) -> &mut Vec { &mut self.queries } /// Removes all the answers from the Message pub fn take_queries(&mut self) -> Vec { mem::take(&mut self.queries) } /// ```text /// Answer Carries RRs which directly answer the query. /// ``` pub fn answers(&self) -> &[Record] { &self.answers } /// Provides mutable access to `answers` pub fn answers_mut(&mut self) -> &mut Vec { &mut self.answers } /// Removes all the answers from the Message pub fn take_answers(&mut self) -> Vec { mem::take(&mut self.answers) } /// ```text /// Authority Carries RRs which describe other authoritative servers. /// May optionally carry the SOA RR for the authoritative /// data in the answer section. /// ``` pub fn name_servers(&self) -> &[Record] { &self.name_servers } /// Provides mutable access to `name_servers` pub fn name_servers_mut(&mut self) -> &mut Vec { &mut self.name_servers } /// Remove the name servers from the Message pub fn take_name_servers(&mut self) -> Vec { mem::take(&mut self.name_servers) } /// ```text /// Additional Carries RRs which may be helpful in using the RRs in the /// other sections. /// ``` pub fn additionals(&self) -> &[Record] { &self.additionals } /// Provides mutable access to `additionals` pub fn additionals_mut(&mut self) -> &mut Vec { &mut self.additionals } /// Remove the additional Records from the Message pub fn take_additionals(&mut self) -> Vec { mem::take(&mut self.additionals) } /// All sections chained pub fn all_sections(&self) -> impl Iterator { self.answers .iter() .chain(self.name_servers().iter()) .chain(self.additionals.iter()) } /// [RFC 6891, EDNS(0) Extensions, April 2013](https://tools.ietf.org/html/rfc6891#section-6.1.1) /// /// ```text /// 6.1.1. Basic Elements /// /// An OPT pseudo-RR (sometimes called a meta-RR) MAY be added to the /// additional data section of a request. /// /// The OPT RR has RR type 41. /// /// If an OPT record is present in a received request, compliant /// responders MUST include an OPT record in their respective responses. /// /// An OPT record does not carry any DNS data. It is used only to /// contain control information pertaining to the question-and-answer /// sequence of a specific transaction. OPT RRs MUST NOT be cached, /// forwarded, or stored in or loaded from Zone Files. /// /// The OPT RR MAY be placed anywhere within the additional data section. /// When an OPT RR is included within any DNS message, it MUST be the /// only OPT RR in that message. If a query message with more than one /// OPT RR is received, a FORMERR (RCODE=1) MUST be returned. The /// placement flexibility for the OPT RR does not override the need for /// the TSIG or SIG(0) RRs to be the last in the additional section /// whenever they are present. /// ``` /// # Return value /// /// Optionally returns a reference to EDNS section #[deprecated(note = "Please use `extensions()`")] pub fn edns(&self) -> Option<&Edns> { self.edns.as_ref() } /// Optionally returns mutable reference to EDNS section #[deprecated( note = "Please use `extensions_mut()`. You can chain `.get_or_insert_with(Edns::new)` to recover original behavior of adding Edns if not present" )] pub fn edns_mut(&mut self) -> &mut Edns { if self.edns.is_none() { self.set_edns(Edns::new()); } self.edns.as_mut().unwrap() } /// Returns reference of Edns section pub fn extensions(&self) -> &Option { &self.edns } /// Returns mutable reference of Edns section pub fn extensions_mut(&mut self) -> &mut Option { &mut self.edns } /// # Return value /// /// the max payload value as it's defined in the EDNS section. pub fn max_payload(&self) -> u16 { let max_size = self.edns.as_ref().map_or(512, Edns::max_payload); if max_size < 512 { 512 } else { max_size } } /// # Return value /// /// the version as defined in the EDNS record pub fn version(&self) -> u8 { self.edns.as_ref().map_or(0, Edns::version) } /// [RFC 2535, Domain Name System Security Extensions, March 1999](https://tools.ietf.org/html/rfc2535#section-4) /// /// ```text /// A DNS request may be optionally signed by including one or more SIGs /// at the end of the query. Such SIGs are identified by having a "type /// covered" field of zero. They sign the preceding DNS request message /// including DNS header but not including the IP header or any request /// SIGs at the end and before the request RR counts have been adjusted /// for the inclusions of any request SIG(s). /// ``` /// /// # Return value /// /// The sig0 and tsig, i.e. signed record, for verifying the sending and package integrity // comportment change: can now return TSIG instead of SIG0. Maybe should get deprecated in // favor of signature() which have more correct naming ? pub fn sig0(&self) -> &[Record] { &self.signature } /// [RFC 2535, Domain Name System Security Extensions, March 1999](https://tools.ietf.org/html/rfc2535#section-4) /// /// ```text /// A DNS request may be optionally signed by including one or more SIGs /// at the end of the query. Such SIGs are identified by having a "type /// covered" field of zero. They sign the preceding DNS request message /// including DNS header but not including the IP header or any request /// SIGs at the end and before the request RR counts have been adjusted /// for the inclusions of any request SIG(s). /// ``` /// /// # Return value /// /// The sig0 and tsig, i.e. signed record, for verifying the sending and package integrity pub fn signature(&self) -> &[Record] { &self.signature } /// Remove signatures from the Message pub fn take_signature(&mut self) -> Vec { mem::take(&mut self.signature) } // TODO: only necessary in tests, should it be removed? /// this is necessary to match the counts in the header from the record sections /// this happens implicitly on write_to, so no need to call before write_to #[cfg(test)] pub fn update_counts(&mut self) -> &mut Self { self.header = update_header_counts( &self.header, self.truncated(), HeaderCounts { query_count: self.queries.len(), answer_count: self.answers.len(), nameserver_count: self.name_servers.len(), additional_count: self.additionals.len(), }, ); self } /// Attempts to read the specified number of `Query`s pub fn read_queries(decoder: &mut BinDecoder<'_>, count: usize) -> ProtoResult> { let mut queries = Vec::with_capacity(count); for _ in 0..count { queries.push(Query::read(decoder)?); } Ok(queries) } /// Attempts to read the specified number of records /// /// # Returns /// /// This returns a tuple of first standard Records, then a possibly associated Edns, and then finally any optionally associated SIG0 and TSIG records. #[cfg_attr(not(feature = "dnssec"), allow(unused_mut))] pub fn read_records( decoder: &mut BinDecoder<'_>, count: usize, is_additional: bool, ) -> ProtoResult<(Vec, Option, Vec)> { let mut records: Vec = Vec::with_capacity(count); let mut edns: Option = None; let mut sigs: Vec = Vec::with_capacity(if is_additional { 1 } else { 0 }); // sig0 must be last, once this is set, disable. let mut saw_sig0 = false; // tsig must be last, once this is set, disable. let mut saw_tsig = false; for _ in 0..count { let record = Record::read(decoder)?; if saw_tsig { return Err("tsig must be final resource record".into()); } // TSIG must be last and multiple TSIG records are not allowed if !is_additional { if saw_sig0 { return Err("sig0 must be final resource record".into()); } // SIG0 must be last records.push(record) } else { match record.record_type() { #[cfg(feature = "dnssec")] RecordType::SIG => { saw_sig0 = true; sigs.push(record); } #[cfg(feature = "dnssec")] RecordType::TSIG => { if saw_sig0 { return Err("sig0 must be final resource record".into()); } // SIG0 must be last saw_tsig = true; sigs.push(record); } RecordType::OPT => { if saw_sig0 { return Err("sig0 must be final resource record".into()); } // SIG0 must be last if edns.is_some() { return Err("more than one edns record present".into()); } edns = Some((&record).into()); } _ => { if saw_sig0 { return Err("sig0 must be final resource record".into()); } // SIG0 must be last records.push(record); } } } } Ok((records, edns, sigs)) } /// Decodes a message from the buffer. pub fn from_vec(buffer: &[u8]) -> ProtoResult { let mut decoder = BinDecoder::new(buffer); Self::read(&mut decoder) } /// Encodes the Message into a buffer pub fn to_vec(&self) -> Result, ProtoError> { // TODO: this feels like the right place to verify the max packet size of the message, // will need to update the header for truncation and the lengths if we send less than the // full response. This needs to conform with the EDNS settings of the server... let mut buffer = Vec::with_capacity(512); { let mut encoder = BinEncoder::new(&mut buffer); self.emit(&mut encoder)?; } Ok(buffer) } /// Finalize the message prior to sending. /// /// Subsequent to calling this, the Message should not change. #[allow(clippy::match_single_binding)] pub fn finalize( &mut self, finalizer: &MF, inception_time: u32, ) -> ProtoResult> { debug!("finalizing message: {:?}", self); let (finals, verifier): (Vec, Option) = finalizer.finalize_message(self, inception_time)?; // append all records to message for fin in finals { match fin.record_type() { // SIG0's are special, and come at the very end of the message #[cfg(feature = "dnssec")] RecordType::SIG => self.add_sig0(fin), #[cfg(feature = "dnssec")] RecordType::TSIG => self.add_tsig(fin), _ => self.add_additional(fin), }; } Ok(verifier) } /// Consumes `Message` and returns into components pub fn into_parts(self) -> MessageParts { self.into() } } /// Consumes `Message` giving public access to fields in `Message` so they can be /// destructured and taken by value /// ```rust /// use hickory_proto::op::{Message, MessageParts}; /// /// let msg = Message::new(); /// let MessageParts { queries, .. } = msg.into_parts(); /// ``` #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct MessageParts { /// message header pub header: Header, /// message queries pub queries: Vec, /// message answers pub answers: Vec, /// message name_servers pub name_servers: Vec, /// message additional records pub additionals: Vec, /// sig0 or tsig // this can now contains TSIG too. It should probably be renamed to reflect that, but it's a // breaking change pub sig0: Vec, /// optional edns records pub edns: Option, } impl From for MessageParts { fn from(msg: Message) -> Self { let Message { header, queries, answers, name_servers, additionals, signature, edns, } = msg; Self { header, queries, answers, name_servers, additionals, sig0: signature, edns, } } } impl From for Message { fn from(msg: MessageParts) -> Self { let MessageParts { header, queries, answers, name_servers, additionals, sig0, edns, } = msg; Self { header, queries, answers, name_servers, additionals, signature: sig0, edns, } } } impl Deref for Message { type Target = Header; fn deref(&self) -> &Self::Target { &self.header } } /// Alias for a function verifying if a message is properly signed pub type MessageVerifier = Box ProtoResult + Send>; /// A trait for performing final amendments to a Message before it is sent. /// /// An example of this is a SIG0 signer, which needs the final form of the message, /// but then needs to attach additional data to the body of the message. pub trait MessageFinalizer: Send + Sync + 'static { /// The message taken in should be processed and then return [`Record`]s which should be /// appended to the additional section of the message. /// /// # Arguments /// /// * `message` - message to process /// * `current_time` - the current time as specified by the system, it's not recommended to read the current time as that makes testing complicated. /// /// # Return /// /// A vector to append to the additionals section of the message, sorted in the order as they should appear in the message. fn finalize_message( &self, message: &Message, current_time: u32, ) -> ProtoResult<(Vec, Option)>; /// Return whether the message requires further processing before being sent /// By default, returns true for AXFR and IXFR queries, and Update and Notify messages fn should_finalize_message(&self, message: &Message) -> bool { [OpCode::Update, OpCode::Notify].contains(&message.op_code()) || message .queries() .iter() .any(|q| [RecordType::AXFR, RecordType::IXFR].contains(&q.query_type())) } } /// A MessageFinalizer which does nothing /// /// *WARNING* This should only be used in None context, it will panic in all cases where finalize is called. #[derive(Clone, Copy, Debug)] pub struct NoopMessageFinalizer; impl NoopMessageFinalizer { /// Always returns None pub fn new() -> Option> { None } } impl MessageFinalizer for NoopMessageFinalizer { fn finalize_message( &self, _: &Message, _: u32, ) -> ProtoResult<(Vec, Option)> { panic!("Misused NoopMessageFinalizer, None should be used instead") } fn should_finalize_message(&self, _: &Message) -> bool { true } } /// Returns the count written and a boolean if it was truncated pub fn count_was_truncated(result: ProtoResult) -> ProtoResult<(usize, bool)> { result.map(|count| (count, false)).or_else(|e| { if let ProtoErrorKind::NotAllRecordsWritten { count } = e.kind() { return Ok((*count, true)); } Err(e) }) } /// A trait that defines types which can be emitted as a set, with the associated count returned. pub trait EmitAndCount { /// Emit self to the encoder and return the count of items fn emit(&mut self, encoder: &mut BinEncoder<'_>) -> ProtoResult; } impl<'e, I: Iterator, E: 'e + BinEncodable> EmitAndCount for I { fn emit(&mut self, encoder: &mut BinEncoder<'_>) -> ProtoResult { encoder.emit_all(self) } } /// Emits the different sections of a message properly /// /// # Return /// /// In the case of a successful emit, the final header (updated counts, etc) is returned for help with logging, etc. #[allow(clippy::too_many_arguments)] pub fn emit_message_parts( header: &Header, queries: &mut Q, answers: &mut A, name_servers: &mut N, additionals: &mut D, edns: Option<&Edns>, signature: &[Record], encoder: &mut BinEncoder<'_>, ) -> ProtoResult
where Q: EmitAndCount, A: EmitAndCount, N: EmitAndCount, D: EmitAndCount, { let include_signature: bool = encoder.mode() != EncodeMode::Signing; let place = encoder.place::
()?; let query_count = queries.emit(encoder)?; // TODO: need to do something on max records // return offset of last emitted record. let answer_count = count_was_truncated(answers.emit(encoder))?; let nameserver_count = count_was_truncated(name_servers.emit(encoder))?; let mut additional_count = count_was_truncated(additionals.emit(encoder))?; if let Some(mut edns) = edns.cloned() { // need to commit the error code edns.set_rcode_high(header.response_code().high()); let count = count_was_truncated(encoder.emit_all(iter::once(&Record::from(&edns))))?; additional_count.0 += count.0; additional_count.1 |= count.1; } else if header.response_code().high() > 0 { warn!( "response code: {} for request: {} requires EDNS but none available", header.response_code(), header.id() ); } // this is a little hacky, but if we are Verifying a signature, i.e. the original Message // then the SIG0 records should not be encoded and the edns record (if it exists) is already // part of the additionals section. if include_signature { let count = count_was_truncated(encoder.emit_all(signature.iter()))?; additional_count.0 += count.0; additional_count.1 |= count.1; } let counts = HeaderCounts { query_count, answer_count: answer_count.0, nameserver_count: nameserver_count.0, additional_count: additional_count.0, }; let was_truncated = header.truncated() || answer_count.1 || nameserver_count.1 || additional_count.1; let final_header = update_header_counts(header, was_truncated, counts); place.replace(encoder, final_header)?; Ok(final_header) } impl BinEncodable for Message { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { emit_message_parts( &self.header, &mut self.queries.iter(), &mut self.answers.iter(), &mut self.name_servers.iter(), &mut self.additionals.iter(), self.edns.as_ref(), &self.signature, encoder, )?; Ok(()) } } impl<'r> BinDecodable<'r> for Message { fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult { let mut header = Header::read(decoder)?; // TODO: return just header, and in the case of the rest of message getting an error. // this could improve error detection while decoding. // get the questions let count = header.query_count() as usize; let mut queries = Vec::with_capacity(count); for _ in 0..count { queries.push(Query::read(decoder)?); } // get all counts before header moves let answer_count = header.answer_count() as usize; let name_server_count = header.name_server_count() as usize; let additional_count = header.additional_count() as usize; let (answers, _, _) = Self::read_records(decoder, answer_count, false)?; let (name_servers, _, _) = Self::read_records(decoder, name_server_count, false)?; let (additionals, edns, signature) = Self::read_records(decoder, additional_count, true)?; // need to grab error code from EDNS (which might have a higher value) if let Some(edns) = &edns { let high_response_code = edns.rcode_high(); header.merge_response_code(high_response_code); } Ok(Self { header, queries, answers, name_servers, additionals, signature, edns, }) } } impl fmt::Display for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let write_query = |slice, f: &mut fmt::Formatter<'_>| -> Result<(), fmt::Error> { for d in slice { writeln!(f, ";; {d}")?; } Ok(()) }; let write_slice = |slice, f: &mut fmt::Formatter<'_>| -> Result<(), fmt::Error> { for d in slice { writeln!(f, "{d}")?; } Ok(()) }; writeln!(f, "; header {header}", header = self.header())?; if let Some(edns) = self.extensions() { writeln!(f, "; edns {edns}")?; } writeln!(f, "; query")?; write_query(self.queries(), f)?; if self.header().message_type() == MessageType::Response || self.header().op_code() == OpCode::Update { writeln!(f, "; answers {}", self.answer_count())?; write_slice(self.answers(), f)?; writeln!(f, "; nameservers {}", self.name_server_count())?; write_slice(self.name_servers(), f)?; writeln!(f, "; additionals {}", self.additional_count())?; write_slice(self.additionals(), f)?; } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_emit_and_read_header() { let mut message = Message::new(); message .set_id(10) .set_message_type(MessageType::Response) .set_op_code(OpCode::Update) .set_authoritative(true) .set_truncated(false) .set_recursion_desired(true) .set_recursion_available(true) .set_response_code(ResponseCode::ServFail); test_emit_and_read(message); } #[test] fn test_emit_and_read_query() { let mut message = Message::new(); message .set_id(10) .set_message_type(MessageType::Response) .set_op_code(OpCode::Update) .set_authoritative(true) .set_truncated(true) .set_recursion_desired(true) .set_recursion_available(true) .set_response_code(ResponseCode::ServFail) .add_query(Query::new()) .update_counts(); // we're not testing the query parsing, just message test_emit_and_read(message); } #[test] fn test_emit_and_read_records() { let mut message = Message::new(); message .set_id(10) .set_message_type(MessageType::Response) .set_op_code(OpCode::Update) .set_authoritative(true) .set_truncated(true) .set_recursion_desired(true) .set_recursion_available(true) .set_authentic_data(true) .set_checking_disabled(true) .set_response_code(ResponseCode::ServFail); message.add_answer(Record::new()); message.add_name_server(Record::new()); message.add_additional(Record::new()); message.update_counts(); // needed for the comparison... test_emit_and_read(message); } #[cfg(test)] fn test_emit_and_read(message: Message) { let mut byte_vec: Vec = Vec::with_capacity(512); { let mut encoder = BinEncoder::new(&mut byte_vec); message.emit(&mut encoder).unwrap(); } let mut decoder = BinDecoder::new(&byte_vec); let got = Message::read(&mut decoder).unwrap(); assert_eq!(got, message); } #[test] fn test_legit_message() { #[rustfmt::skip] let buf: Vec = vec![ 0x10, 0x00, 0x81, 0x80, // id = 4096, response, op=query, recursion_desired, recursion_available, no_error 0x00, 0x01, 0x00, 0x01, // 1 query, 1 answer, 0x00, 0x00, 0x00, 0x00, // 0 nameservers, 0 additional record 0x03, b'w', b'w', b'w', // query --- www.example.com 0x07, b'e', b'x', b'a', // b'm', b'p', b'l', b'e', // 0x03, b'c', b'o', b'm', // 0x00, // 0 = endname 0x00, 0x01, 0x00, 0x01, // ReordType = A, Class = IN 0xC0, 0x0C, // name pointer to www.example.com 0x00, 0x01, 0x00, 0x01, // RecordType = A, Class = IN 0x00, 0x00, 0x00, 0x02, // TTL = 2 seconds 0x00, 0x04, // record length = 4 (ipv4 address) 0x5D, 0xB8, 0xD8, 0x22, // address = 93.184.216.34 ]; let mut decoder = BinDecoder::new(&buf); let message = Message::read(&mut decoder).unwrap(); assert_eq!(message.id(), 4096); let mut buf: Vec = Vec::with_capacity(512); { let mut encoder = BinEncoder::new(&mut buf); message.emit(&mut encoder).unwrap(); } let mut decoder = BinDecoder::new(&buf); let message = Message::read(&mut decoder).unwrap(); assert_eq!(message.id(), 4096); } #[test] fn rdata_zero_roundtrip() { let buf = &[ 160, 160, 0, 13, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, ]; assert!(Message::from_bytes(buf).is_err()); } #[test] fn nsec_deserialization() { const CRASHING_MESSAGE: &[u8] = &[ 0, 0, 132, 0, 0, 0, 0, 1, 0, 0, 0, 1, 36, 49, 101, 48, 101, 101, 51, 100, 51, 45, 100, 52, 50, 52, 45, 52, 102, 55, 56, 45, 57, 101, 52, 99, 45, 99, 51, 56, 51, 51, 55, 55, 56, 48, 102, 50, 98, 5, 108, 111, 99, 97, 108, 0, 0, 1, 128, 1, 0, 0, 0, 120, 0, 4, 192, 168, 1, 17, 36, 49, 101, 48, 101, 101, 51, 100, 51, 45, 100, 52, 50, 52, 45, 52, 102, 55, 56, 45, 57, 101, 52, 99, 45, 99, 51, 56, 51, 51, 55, 55, 56, 48, 102, 50, 98, 5, 108, 111, 99, 97, 108, 0, 0, 47, 128, 1, 0, 0, 0, 120, 0, 5, 192, 70, 0, 1, 64, ]; Message::from_vec(CRASHING_MESSAGE).expect("failed to parse message"); } } hickory-proto-0.24.0/src/op/mod.rs000064400000000000000000000024351046102023000150560ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! Operations to send with a `Client` or server, e.g. `Query`, `Message`, or `UpdateMessage` can //! be used together to either query or update resource records sets. mod edns; pub mod header; mod lower_query; pub mod message; pub mod op_code; pub mod query; pub mod response_code; pub mod update_message; pub use self::edns::Edns; pub use self::header::Header; pub use self::header::MessageType; pub use self::message::{ Message, MessageFinalizer, MessageParts, MessageVerifier, NoopMessageFinalizer, }; pub use self::op_code::OpCode; pub use self::query::Query; pub use self::response_code::ResponseCode; pub use lower_query::LowerQuery; pub use update_message::UpdateMessage; hickory-proto-0.24.0/src/op/op_code.rs000064400000000000000000000057561046102023000157200ustar 00000000000000// Copyright 2015-2021 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Operation code for queries, updates, and responses use std::{convert::From, fmt}; use crate::error::*; /// Operation code for queries, updates, and responses /// /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035) /// /// ```text /// OPCODE A four bit field that specifies kind of query in this /// message. This value is set by the originator of a query /// and copied into the response. The values are: /// /// 0 a standard query (QUERY) /// /// 1 an inverse query (IQUERY) /// /// 2 a server status request (STATUS) /// /// 3-15 reserved for future use /// ``` #[derive(Debug, PartialEq, Eq, PartialOrd, Copy, Clone, Hash)] #[allow(dead_code)] pub enum OpCode { /// Query request [RFC 1035](https://tools.ietf.org/html/rfc1035) Query, /// Status message [RFC 1035](https://tools.ietf.org/html/rfc1035) Status, /// Notify of change [RFC 1996](https://tools.ietf.org/html/rfc1996) Notify, /// Update message [RFC 2136](https://tools.ietf.org/html/rfc2136) Update, } impl fmt::Display for OpCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let s = match self { Self::Query => "QUERY", Self::Status => "STATUS", Self::Notify => "NOTIFY", Self::Update => "UPDATE", }; f.write_str(s) } } /// Convert from `OpCode` to `u8` /// /// ``` /// use hickory_proto::op::op_code::OpCode; /// /// let var: u8 = From::from(OpCode::Query); /// assert_eq!(0, var); /// /// let var: u8 = OpCode::Query.into(); /// assert_eq!(0, var); /// ``` impl From for u8 { fn from(rt: OpCode) -> Self { match rt { OpCode::Query => 0, // 1 IQuery (Inverse Query, OBSOLETE) [RFC3425] OpCode::Status => 2, // 3 Unassigned OpCode::Notify => 4, OpCode::Update => 5, // 6-15 Unassigned } } } /// Convert from `u8` to `OpCode` /// /// ``` /// use hickory_proto::op::op_code::OpCode; /// /// let var: OpCode = OpCode::from_u8(0).unwrap(); /// assert_eq!(OpCode::Query, var); /// ``` impl OpCode { /// Decodes the binary value of the OpCode pub fn from_u8(value: u8) -> ProtoResult { match value { 0 => Ok(Self::Query), 2 => Ok(Self::Status), 4 => Ok(Self::Notify), 5 => Ok(Self::Update), _ => Err(format!("unknown OpCode: {value}").into()), } } } hickory-proto-0.24.0/src/op/query.rs000064400000000000000000000247371046102023000154550ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! Query struct for looking up resource records use std::fmt; use std::fmt::{Display, Formatter}; use crate::error::*; use crate::rr::dns_class::DNSClass; use crate::rr::domain::Name; use crate::rr::record_type::RecordType; use crate::serialize::binary::*; #[cfg(feature = "mdns")] /// From [RFC 6762](https://tools.ietf.org/html/rfc6762#section-5.4) /// ```text // To avoid large floods of potentially unnecessary responses in these // cases, Multicast DNS defines the top bit in the class field of a DNS // question as the unicast-response bit. /// ``` const MDNS_UNICAST_RESPONSE: u16 = 1 << 15; /// Query struct for looking up resource records, basically a resource record without RDATA. /// /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035) /// /// ```text /// 4.1.2. Question section format /// /// The question section is used to carry the "question" in most queries, /// i.e., the parameters that define what is being asked. The section /// contains QDCOUNT (usually 1) entries, each of the following format: /// /// 1 1 1 1 1 1 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// | | /// / QNAME / ZNAME / /// / / /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// | QTYPE / ZTYPE | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// | QCLASS / ZCLASS | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ /// /// ``` #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Query { name: Name, query_type: RecordType, query_class: DNSClass, #[cfg(feature = "mdns")] mdns_unicast_response: bool, } impl Default for Query { /// Return a default query with an empty name and A, IN for the query_type and query_class fn default() -> Self { Self { name: Name::new(), query_type: RecordType::A, query_class: DNSClass::IN, #[cfg(feature = "mdns")] mdns_unicast_response: false, } } } impl Query { /// Return a default query with an empty name and A, IN for the query_type and query_class pub fn new() -> Self { Self::default() } /// Create a new query from name and type, class defaults to IN #[allow(clippy::self_named_constructors)] pub fn query(name: Name, query_type: RecordType) -> Self { Self { name, query_type, query_class: DNSClass::IN, #[cfg(feature = "mdns")] mdns_unicast_response: false, } } /// replaces name with the new name pub fn set_name(&mut self, name: Name) -> &mut Self { self.name = name; self } /// Specify the RecordType being queried pub fn set_query_type(&mut self, query_type: RecordType) -> &mut Self { self.query_type = query_type; self } /// Specify÷ the DNS class of the Query, almost always IN pub fn set_query_class(&mut self, query_class: DNSClass) -> &mut Self { self.query_class = query_class; self } /// Changes mDNS unicast-response bit /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-5.4) #[cfg(feature = "mdns")] #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))] pub fn set_mdns_unicast_response(&mut self, flag: bool) -> &mut Self { self.mdns_unicast_response = flag; self } /// ```text /// QNAME a domain name represented as a sequence of labels, where /// each label consists of a length octet followed by that /// number of octets. The domain name terminates with the /// zero length octet for the null label of the root. Note /// that this field may be an odd number of octets; no /// padding is used. /// ``` pub fn name(&self) -> &Name { &self.name } /// ```text /// QTYPE a two octet code which specifies the type of the query. /// The values for this field include all codes valid for a /// TYPE field, together with some more general codes which /// can match more than one type of RR. /// ``` pub fn query_type(&self) -> RecordType { self.query_type } /// ```text /// QCLASS a two octet code that specifies the class of the query. /// For example, the QCLASS field is IN for the Internet. /// ``` pub fn query_class(&self) -> DNSClass { self.query_class } /// Returns if the mDNS unicast-response bit is set or not /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-5.4) #[cfg(feature = "mdns")] #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))] pub fn mdns_unicast_response(&self) -> bool { self.mdns_unicast_response } /// Consumes `Query` and returns it's components pub fn into_parts(self) -> QueryParts { self.into() } } /// Consumes `Query` giving public access to fields of `Query` so they can /// be destructured and taken by value. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct QueryParts { /// QNAME pub name: Name, /// QTYPE pub query_type: RecordType, /// QCLASS pub query_class: DNSClass, /// mDNS unicast-response bit set or not #[cfg(feature = "mdns")] #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))] pub mdns_unicast_response: bool, } impl From for QueryParts { fn from(q: Query) -> Self { cfg_if::cfg_if! { if #[cfg(feature = "mdns")] { let Query { name, query_type, query_class, mdns_unicast_response, } = q; } else { let Query { name, query_type, query_class, } = q; } } Self { name, query_type, query_class, #[cfg(feature = "mdns")] mdns_unicast_response, } } } impl BinEncodable for Query { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { self.name.emit(encoder)?; self.query_type.emit(encoder)?; #[cfg(not(feature = "mdns"))] self.query_class.emit(encoder)?; #[cfg(feature = "mdns")] { if self.mdns_unicast_response { encoder.emit_u16(u16::from(self.query_class()) | MDNS_UNICAST_RESPONSE)?; } else { self.query_class.emit(encoder)?; } } Ok(()) } } impl<'r> BinDecodable<'r> for Query { fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult { let name = Name::read(decoder)?; let query_type = RecordType::read(decoder)?; #[cfg(feature = "mdns")] let mut mdns_unicast_response = false; #[cfg(not(feature = "mdns"))] let query_class = DNSClass::read(decoder)?; #[cfg(feature = "mdns")] let query_class = { let query_class_value = decoder.read_u16()?.unverified(/*DNSClass::from_u16 will verify the value*/); if query_class_value & MDNS_UNICAST_RESPONSE > 0 { mdns_unicast_response = true; DNSClass::from(query_class_value & !MDNS_UNICAST_RESPONSE) } else { DNSClass::from(query_class_value) } }; Ok(Self { name, query_type, query_class, #[cfg(feature = "mdns")] mdns_unicast_response, }) } } impl Display for Query { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { #[cfg(not(feature = "mdns"))] { write!( f, "{name} {class} {ty}", name = self.name, class = self.query_class, ty = self.query_type, ) } #[cfg(feature = "mdns")] { write!( f, "{name} {class} {ty}; mdns_unicast_response: {mdns}", name = self.name, class = self.query_class, ty = self.query_type, mdns = self.mdns_unicast_response ) } } } #[test] #[allow(clippy::needless_update)] fn test_read_and_emit() { let expect = Query { name: Name::from_ascii("WWW.example.com").unwrap(), query_type: RecordType::AAAA, query_class: DNSClass::IN, ..Query::default() }; let mut byte_vec: Vec = Vec::with_capacity(512); { let mut encoder = BinEncoder::new(&mut byte_vec); expect.emit(&mut encoder).unwrap(); } let mut decoder = BinDecoder::new(&byte_vec); let got = Query::read(&mut decoder).unwrap(); assert_eq!(got, expect); } #[cfg(feature = "mdns")] #[test] fn test_mdns_unicast_response_bit_handling() { const QCLASS_OFFSET: usize = 1 /* empty name */ + std::mem::size_of::() /* query_type */; let mut query = Query::new(); query.set_mdns_unicast_response(true); let mut vec_bytes: Vec = Vec::with_capacity(512); { let mut encoder = BinEncoder::new(&mut vec_bytes); query.emit(&mut encoder).unwrap(); let query_class_slice = encoder.slice_of(QCLASS_OFFSET, QCLASS_OFFSET + 2); assert_eq!(query_class_slice, &[0x80, 0x01]); } let mut decoder = BinDecoder::new(&vec_bytes); let got = Query::read(&mut decoder).unwrap(); assert_eq!(got.query_class(), DNSClass::IN); assert!(got.mdns_unicast_response()); } hickory-proto-0.24.0/src/op/response_code.rs000064400000000000000000000336321046102023000171320ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // there is not much to format in this file, and we don't want rustfmt to mess up the comments //! All defined response codes in DNS use std::fmt; use std::fmt::{Display, Formatter}; /// The status code of the response to a query. /// /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035) /// /// ```text /// RCODE Response code - this 4 bit field is set as part of /// responses. The values have the following /// interpretation: /// /// 0 No error condition /// /// 1 Format error - The name server was /// unable to interpret the query. /// /// 2 Server failure - The name server was /// unable to process this query due to a /// problem with the name server. /// /// 3 Name Error - Meaningful only for /// responses from an authoritative name /// server, this code signifies that the /// domain name referenced in the query does /// not exist. /// /// 4 Not Implemented - The name server does /// not support the requested kind of query. /// /// 5 Refused - The name server refuses to /// perform the specified operation for /// policy reasons. For example, a name /// server may not wish to provide the /// information to the particular requester, /// or a name server may not wish to perform /// a particular operation (e.g., zone /// transfer) for particular data. /// /// 6-15 Reserved for future use. /// ``` #[derive(Debug, Eq, PartialEq, PartialOrd, Copy, Clone, Hash)] #[allow(dead_code)] pub enum ResponseCode { /// No Error [RFC 1035](https://tools.ietf.org/html/rfc1035) NoError, /// Format Error [RFC 1035](https://tools.ietf.org/html/rfc1035) FormErr, /// Server Failure [RFC 1035](https://tools.ietf.org/html/rfc1035) ServFail, /// Non-Existent Domain [RFC 1035](https://tools.ietf.org/html/rfc1035) NXDomain, /// Not Implemented [RFC 1035](https://tools.ietf.org/html/rfc1035) NotImp, /// Query Refused [RFC 1035](https://tools.ietf.org/html/rfc1035) Refused, /// Name Exists when it should not [RFC 2136](https://tools.ietf.org/html/rfc2136) YXDomain, /// RR Set Exists when it should not [RFC 2136](https://tools.ietf.org/html/rfc2136) YXRRSet, /// RR Set that should exist does not [RFC 2136](https://tools.ietf.org/html/rfc2136) NXRRSet, /// Server Not Authoritative for zone [RFC 2136](https://tools.ietf.org/html/rfc2136) /// or Not Authorized [RFC 8945](https://www.rfc-editor.org/rfc/rfc8945) NotAuth, /// Name not contained in zone [RFC 2136](https://tools.ietf.org/html/rfc2136) NotZone, /// Bad OPT Version [RFC 6891](https://tools.ietf.org/html/rfc6891#section-9) BADVERS, /// TSIG Signature Failure [RFC 8945](https://www.rfc-editor.org/rfc/rfc8945) BADSIG, /// Key not recognized [RFC 8945](https://www.rfc-editor.org/rfc/rfc8945) BADKEY, /// Signature out of time window [RFC 8945](https://www.rfc-editor.org/rfc/rfc8945) BADTIME, /// Bad TKEY Mode [RFC 2930](https://tools.ietf.org/html/rfc2930#section-2.6) BADMODE, /// Duplicate key name [RFC 2930](https://tools.ietf.org/html/rfc2930#section-2.6) BADNAME, /// Algorithm not supported [RFC 2930](https://tools.ietf.org/html/rfc2930#section-2.6) BADALG, /// Bad Truncation [RFC 4635](https://tools.ietf.org/html/rfc4635#section-4) BADTRUNC, /// Bad/missing server cookie [draft-ietf-dnsop-cookies](https://tools.ietf.org/html/draft-ietf-dnsop-cookies-10) BADCOOKIE, // 24-3840 Unassigned // 3841-4095 Reserved for Private Use [RFC6895] // 4096-65534 Unassigned // 65535 Reserved, can be allocated by Standards Action [RFC6895] /// An unknown or unregistered response code was received. Unknown(u16), } impl ResponseCode { /// returns the lower 4 bits of the response code (for the original header portion of the code) pub fn low(self) -> u8 { (u16::from(self) & 0x000F) as u8 } /// returns the high 8 bits for the EDNS portion of the response code pub fn high(self) -> u8 { ((u16::from(self) & 0x0FF0) >> 4) as u8 } /// DNS can not store the entire space of ResponseCodes in 4 bit space of the Header, this function /// allows for a initial value of the first 4 bits to be set. /// /// After the EDNS is read, the entire ResponseCode (12 bits) can be reconstructed for the full ResponseCode. pub fn from_low(low: u8) -> Self { ((u16::from(low)) & 0x000F).into() } /// Combines the EDNS high and low from the Header to produce the Extended ResponseCode pub fn from(high: u8, low: u8) -> Self { ((u16::from(high) << 4) | ((u16::from(low)) & 0x000F)).into() } /// Transforms the response code into the human message pub fn to_str(self) -> &'static str { match self { Self::NoError => "No Error", Self::FormErr => "Form Error", // 1 FormErr Format Error [RFC1035] Self::ServFail => "Server Failure", // 2 ServFail Server Failure [RFC1035] Self::NXDomain => "Non-Existent Domain", // 3 NXDomain Non-Existent Domain [RFC1035] Self::NotImp => "Not Implemented", // 4 NotImp Not Implemented [RFC1035] Self::Refused => "Query Refused", // 5 Refused Query Refused [RFC1035] Self::YXDomain => "Name should not exist", // 6 YXDomain Name Exists when it should not [RFC2136][RFC6672] Self::YXRRSet => "RR Set should not exist", // 7 YXRRSet RR Set Exists when it should not [RFC2136] Self::NXRRSet => "RR Set does not exist", // 8 NXRRSet RR Set that should exist does not [RFC2136] Self::NotAuth => "Not authorized", // 9 NotAuth Server Not Authoritative for zone [RFC2136] Self::NotZone => "Name not in zone", // 10 NotZone Name not contained in zone [RFC2136] Self::BADVERS => "Bad option verions", // 16 BADVERS Bad OPT Version [RFC6891] Self::BADSIG => "TSIG Failure", // 16 BADSIG TSIG Signature Failure [RFC2845] Self::BADKEY => "Key not recognized", // 17 BADKEY Key not recognized [RFC2845] Self::BADTIME => "Signature out of time window", // 18 BADTIME Signature out of time window [RFC2845] Self::BADMODE => "Bad TKEY mode", // 19 BADMODE Bad TKEY Mode [RFC2930] Self::BADNAME => "Duplicate key name", // 20 BADNAME Duplicate key name [RFC2930] Self::BADALG => "Algorithm not supported", // 21 BADALG Algorithm not supported [RFC2930] Self::BADTRUNC => "Bad truncation", // 22 BADTRUNC Bad Truncation [RFC4635] Self::BADCOOKIE => "Bad server cookie", // 23 BADCOOKIE (TEMPORARY - registered 2015-07-26, expires 2016-07-26) Bad/missing server cookie [draft-ietf-dnsop-cookies] Self::Unknown(_) => "Unknown response code", } } } impl Default for ResponseCode { fn default() -> Self { Self::NoError } } impl Display for ResponseCode { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { f.write_str(self.to_str()) } } /// Convert from `ResponseCode` to `u16` /// /// ``` /// use hickory_proto::op::response_code::ResponseCode; /// /// let var: ResponseCode = From::from(0); /// assert_eq!(ResponseCode::NoError, var); /// /// let var: ResponseCode = 0.into(); /// assert_eq!(ResponseCode::NoError, var); /// ``` impl From for u16 { fn from(rt: ResponseCode) -> Self { match rt { ResponseCode::NoError => 0, // 0 NoError No Error [RFC1035] ResponseCode::FormErr => 1, // 1 FormErr Format Error [RFC1035] ResponseCode::ServFail => 2, // 2 ServFail Server Failure [RFC1035] ResponseCode::NXDomain => 3, // 3 NXDomain Non-Existent Domain [RFC1035] ResponseCode::NotImp => 4, // 4 NotImp Not Implemented [RFC1035] ResponseCode::Refused => 5, // 5 Refused Query Refused [RFC1035] ResponseCode::YXDomain => 6, // 6 YXDomain Name Exists when it should not [RFC2136][RFC6672] ResponseCode::YXRRSet => 7, // 7 YXRRSet RR Set Exists when it should not [RFC2136] ResponseCode::NXRRSet => 8, // 8 NXRRSet RR Set that should exist does not [RFC2136] ResponseCode::NotAuth => 9, // 9 NotAuth Server Not Authoritative for zone [RFC2136] ResponseCode::NotZone => 10, // 10 NotZone Name not contained in zone [RFC2136] // // 11-15 Unassigned // // 16 BADVERS Bad OPT Version [RFC6891] // 16 BADSIG TSIG Signature Failure [RFC2845] ResponseCode::BADVERS | ResponseCode::BADSIG => 16, ResponseCode::BADKEY => 17, // 17 BADKEY Key not recognized [RFC2845] ResponseCode::BADTIME => 18, // 18 BADTIME Signature out of time window [RFC2845] ResponseCode::BADMODE => 19, // 19 BADMODE Bad TKEY Mode [RFC2930] ResponseCode::BADNAME => 20, // 20 BADNAME Duplicate key name [RFC2930] ResponseCode::BADALG => 21, // 21 BADALG Algorithm not supported [RFC2930] ResponseCode::BADTRUNC => 22, // 22 BADTRUNC Bad Truncation [RFC4635] // 23 BADCOOKIE (TEMPORARY - registered 2015-07-26, expires 2016-07-26) Bad/missing server cookie [draft-ietf-dnsop-cookies] ResponseCode::BADCOOKIE => 23, ResponseCode::Unknown(code) => code, } } } /// Convert from `u16` to `ResponseCode` /// /// ``` /// use hickory_proto::op::response_code::ResponseCode; /// /// let var: u16 = From::from(ResponseCode::NoError); /// assert_eq!(0, var); /// /// let var: u16 = ResponseCode::NoError.into(); /// assert_eq!(0, var); /// ``` impl From for ResponseCode { #[allow(clippy::unimplemented)] fn from(value: u16) -> Self { match value { 0 => Self::NoError, // 0 NoError No Error [RFC1035] 1 => Self::FormErr, // 1 FormErr Format Error [RFC1035] 2 => Self::ServFail, // 2 ServFail Server Failure [RFC1035] 3 => Self::NXDomain, // 3 NXDomain Non-Existent Domain [RFC1035] 4 => Self::NotImp, // 4 NotImp Not Implemented [RFC1035] 5 => Self::Refused, // 5 Refused Query Refused [RFC1035] 6 => Self::YXDomain, // 6 YXDomain Name Exists when it should not [RFC2136][RFC6672] 7 => Self::YXRRSet, // 7 YXRRSet RR Set Exists when it should not [RFC2136] 8 => Self::NXRRSet, // 8 NXRRSet RR Set that should exist does not [RFC2136] 9 => Self::NotAuth, // 9 NotAuth Server Not Authoritative for zone [RFC2136] 10 => Self::NotZone, // 10 NotZone Name not contained in zone [RFC2136] // this looks to be backwards compat for 4 bit ResponseCodes. // 16 BADVERS Bad OPT Version [RFC6891] // 16 => ResponseCode::BADVERS, 16 => Self::BADSIG, // 16 BADSIG TSIG Signature Failure [RFC2845] 17 => Self::BADKEY, // 17 BADKEY Key not recognized [RFC2845] 18 => Self::BADTIME, // 18 BADTIME Signature out of time window [RFC2845] 19 => Self::BADMODE, // 19 BADMODE Bad TKEY Mode [RFC2930] 20 => Self::BADNAME, // 20 BADNAME Duplicate key name [RFC2930] 21 => Self::BADALG, // 21 BADALG Algorithm not supported [RFC2930] 22 => Self::BADTRUNC, // 22 BADTRUNC Bad Truncation [RFC4635] 23 => Self::BADCOOKIE, // 23 BADCOOKIE (TEMPORARY - registered 2015-07-26, expires 2016-07-26) Bad/missing server cookie [draft-ietf-dnsop-cookies] code => Self::Unknown(code), } } } hickory-proto-0.24.0/src/op/update_message.rs000064400000000000000000000516451046102023000172740ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Update related operations for Messages use std::fmt::Debug; use crate::{ op::{Edns, Message, MessageType, OpCode, Query}, rr::{ rdata::{NULL, SOA}, DNSClass, Name, RData, Record, RecordSet, RecordType, }, }; /// To reduce errors in using the Message struct as an Update, this will do the call throughs /// to properly do that. /// /// Generally rather than constructing this by hand, see the update methods on `Client` pub trait UpdateMessage: Debug { /// see `Header::id` fn id(&self) -> u16; /// Adds the zone section, i.e. name.example.com would be example.com fn add_zone(&mut self, query: Query); /// Add the pre-requisite records /// /// These must exist, or not, for the Update request to go through. fn add_pre_requisite(&mut self, record: Record); /// Add all the Records from the Iterator to the pre-requisites section fn add_pre_requisites(&mut self, records: R) where R: IntoIterator, I: Iterator; /// Add the Record to be updated fn add_update(&mut self, record: Record); /// Add the Records from the Iterator to the updates section fn add_updates(&mut self, records: R) where R: IntoIterator, I: Iterator; /// Add Records to the additional Section of the UpdateMessage fn add_additional(&mut self, record: Record); /// Returns the Zones to be updated, generally should only be one. fn zones(&self) -> &[Query]; /// Returns the pre-requisites fn prerequisites(&self) -> &[Record]; /// Returns the records to be updated fn updates(&self) -> &[Record]; /// Returns the additional records fn additionals(&self) -> &[Record]; /// This is used to authenticate update messages. /// /// see `Message::sig0()` for more information. fn sig0(&self) -> &[Record]; } /// to reduce errors in using the Message struct as an Update, this will do the call throughs /// to properly do that. impl UpdateMessage for Message { fn id(&self) -> u16 { self.id() } fn add_zone(&mut self, query: Query) { self.add_query(query); } fn add_pre_requisite(&mut self, record: Record) { self.add_answer(record); } fn add_pre_requisites(&mut self, records: R) where R: IntoIterator, I: Iterator, { self.add_answers(records); } fn add_update(&mut self, record: Record) { self.add_name_server(record); } fn add_updates(&mut self, records: R) where R: IntoIterator, I: Iterator, { self.add_name_servers(records); } fn add_additional(&mut self, record: Record) { self.add_additional(record); } fn zones(&self) -> &[Query] { self.queries() } fn prerequisites(&self) -> &[Record] { self.answers() } fn updates(&self) -> &[Record] { self.name_servers() } fn additionals(&self) -> &[Record] { self.additionals() } fn sig0(&self) -> &[Record] { self.sig0() } } /// Sends a record to create on the server, this will fail if the record exists (atomicity /// depends on the server) /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.3 - RRset Does Not Exist /// /// No RRs with a specified NAME and TYPE (in the zone and class denoted /// by the Zone Section) can exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the RRset whose nonexistence /// is required. The RDLENGTH of this record is zero (0), and RDATA /// field is therefore empty. CLASS must be specified as NONE in order /// to distinguish this condition from a valid RR whose RDLENGTH is /// naturally zero (0) (for example, the NULL RR). TTL must be specified /// as zero (0). /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to create /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection) pub fn create(rrset: RecordSet, zone_origin: Name, use_edns: bool) -> Message { // TODO: assert non-empty rrset? assert!(zone_origin.zone_of(rrset.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(rrset.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); let mut prerequisite = Record::with(rrset.name().clone(), rrset.record_type(), 0); prerequisite.set_dns_class(DNSClass::NONE); message.add_pre_requisite(prerequisite); message.add_updates(rrset); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Appends a record to an existing rrset, optionally require the rrset to exist (atomicity /// depends on the server) /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to append to an RRSet /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `must_exist` - if true, the request will fail if the record does not exist /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be created. pub fn append(rrset: RecordSet, zone_origin: Name, must_exist: bool, use_edns: bool) -> Message { assert!(zone_origin.zone_of(rrset.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(rrset.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); if must_exist { let mut prerequisite = Record::with(rrset.name().clone(), rrset.record_type(), 0); prerequisite.set_dns_class(DNSClass::ANY); message.add_pre_requisite(prerequisite); } message.add_updates(rrset); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Compares and if it matches, swaps it for the new value (atomicity depends on the server) /// /// ```text /// 2.4.2 - RRset Exists (Value Dependent) /// /// A set of RRs with a specified NAME and TYPE exists and has the same /// members with the same RDATAs as the RRset specified here in this /// section. While RRset ordering is undefined and therefore not /// significant to this comparison, the sets be identical in their /// extent. /// /// For this prerequisite, a requestor adds to the section an entire /// RRset whose preexistence is required. NAME and TYPE are that of the /// RRset being denoted. CLASS is that of the zone. TTL must be /// specified as zero (0) and is ignored when comparing RRsets for /// identity. /// /// 2.5.4 - Delete An RR From An RRset /// /// RRs to be deleted are added to the Update Section. The NAME, TYPE, /// RDLENGTH and RDATA must match the RR being deleted. TTL must be /// specified as zero (0) and will otherwise be ignored by the Primary /// Zone Server. CLASS must be specified as NONE to distinguish this from an /// RR addition. If no such RRs exist, then this Update RR will be /// silently ignored by the Primary Zone Server. /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `current` - the current rrset which must exist for the swap to complete /// * `new` - the new rrset with which to replace the current rrset /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). pub fn compare_and_swap( current: RecordSet, new: RecordSet, zone_origin: Name, use_edns: bool, ) -> Message { assert!(zone_origin.zone_of(current.name())); assert!(zone_origin.zone_of(new.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(new.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); // make sure the record is what is expected let mut prerequisite = current.clone(); prerequisite.set_ttl(0); message.add_pre_requisites(prerequisite); // add the delete for the old record let mut delete = current; // the class must be none for delete delete.set_dns_class(DNSClass::NONE); // the TTL should be 0 delete.set_ttl(0); message.add_updates(delete); // insert the new record... message.add_updates(new); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Deletes a record (by rdata) from an rrset, optionally require the rrset to exist. /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.4 - Delete An RR From An RRset /// /// RRs to be deleted are added to the Update Section. The NAME, TYPE, /// RDLENGTH and RDATA must match the RR being deleted. TTL must be /// specified as zero (0) and will otherwise be ignored by the Primary /// Zone Server. CLASS must be specified as NONE to distinguish this from an /// RR addition. If no such RRs exist, then this Update RR will be /// silently ignored by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to delete from a RRSet, the name, type and rdata must match the /// record to delete /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `signer` - the signer, with private key, to use to sign the request /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be deleted. pub fn delete_by_rdata(mut rrset: RecordSet, zone_origin: Name, use_edns: bool) -> Message { assert!(zone_origin.zone_of(rrset.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(rrset.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); // the class must be none for delete rrset.set_dns_class(DNSClass::NONE); // the TTL should be 0 rrset.set_ttl(0); message.add_updates(rrset); // Extended dns if use_edns { message .extensions_mut() .get_or_insert(Edns::new()) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Deletes an entire rrset, optionally require the rrset to exist. /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.2 - Delete An RRset /// /// One RR is added to the Update Section whose NAME and TYPE are those /// of the RRset to be deleted. TTL must be specified as zero (0) and is /// otherwise not used by the Primary Zone Server. CLASS must be specified as /// ANY. RDLENGTH must be zero (0) and RDATA must therefore be empty. /// If no such RRset exists, then this Update RR will be silently ignored /// by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `record` - The name, class and record_type will be used to match and delete the RecordSet /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be deleted. pub fn delete_rrset(mut record: Record, zone_origin: Name, use_edns: bool) -> Message { assert!(zone_origin.zone_of(record.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(record.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); // the class must be none for an rrset delete record.set_dns_class(DNSClass::ANY); // the TTL should be 0 record.set_ttl(0); // the rdata must be null to delete all rrsets record.set_data(Some(RData::NULL(NULL::new()))); message.add_update(record); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Deletes all records at the specified name /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.5.3 - Delete All RRsets From A Name /// /// One RR is added to the Update Section whose NAME is that of the name /// to be cleansed of RRsets. TYPE must be specified as ANY. TTL must /// be specified as zero (0) and is otherwise not used by the Primary /// Zone Server. CLASS must be specified as ANY. RDLENGTH must be zero (0) /// and RDATA must therefore be empty. If no such RRsets exist, then /// this Update RR will be silently ignored by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `name_of_records` - the name of all the record sets to delete /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `dns_class` - the class of the SOA /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). This /// operation attempts to delete all resource record sets the specified name regardless of /// the record type. pub fn delete_all( name_of_records: Name, zone_origin: Name, dns_class: DNSClass, use_edns: bool, ) -> Message { assert!(zone_origin.zone_of(&name_of_records)); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(dns_class) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); // the TTL should be 0 // the rdata must be null to delete all rrsets // the record type must be any let mut record = Record::with(name_of_records, RecordType::ANY, 0); // the class must be none for an rrset delete record.set_dns_class(DNSClass::ANY); message.add_update(record); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } // not an update per-se, but it fits nicely with other functions here /// Download all records from a zone, or all records modified since given SOA was observed. /// The request will either be a AXFR Query (ask for full zone transfer) if a SOA was not /// provided, or a IXFR Query (incremental zone transfer) if a SOA was provided. /// /// # Arguments /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `last_soa` - the last SOA known, if any. If provided, name must match `zone_origin` pub fn zone_transfer(zone_origin: Name, last_soa: Option) -> Message { if let Some(ref soa) = last_soa { assert_eq!(zone_origin, *soa.mname()); } let mut zone: Query = Query::new(); zone.set_name(zone_origin).set_query_class(DNSClass::IN); if last_soa.is_some() { zone.set_query_type(RecordType::IXFR); } else { zone.set_query_type(RecordType::AXFR); } // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_recursion_desired(false); message.add_zone(zone); if let Some(soa) = last_soa { // for IXFR, old SOA is put as authority to indicate last known version let record = Record::from_rdata(soa.mname().clone(), 0, RData::SOA(soa)); message.add_name_server(record); } // Extended dns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } // TODO: this should be configurable // > An EDNS buffer size of 1232 bytes will avoid fragmentation on nearly all current networks. // https://dnsflagday.net/2020/ /// Maximum payload length for EDNS update messages pub const MAX_PAYLOAD_LEN: u16 = 1232; hickory-proto-0.24.0/src/openssl/mod.rs000064400000000000000000000012011046102023000161110ustar 00000000000000// Copyright 2015-2021 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! TLS protocol related components for DNS over TLS mod tls_client_stream; pub mod tls_server; mod tls_stream; pub use self::tls_client_stream::{TlsClientStream, TlsClientStreamBuilder}; pub use self::tls_stream::{tls_stream_from_existing_tls_stream, TlsStream, TlsStreamBuilder}; hickory-proto-0.24.0/src/openssl/tls_client_stream.rs000064400000000000000000000106031046102023000210530ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::future::Future; use std::io; use std::net::SocketAddr; use std::pin::Pin; use futures_util::TryFutureExt; #[cfg(feature = "mtls")] use openssl::pkcs12::Pkcs12; use openssl::x509::X509; use tokio_openssl::SslStream as TokioTlsStream; use crate::error::ProtoError; use crate::iocompat::AsyncIoStdAsTokio; use crate::iocompat::AsyncIoTokioAsStd; use crate::tcp::{Connect, DnsTcpStream, TcpClientStream}; use crate::xfer::BufDnsStreamHandle; use super::TlsStreamBuilder; /// A Type definition for the TLS stream pub type TlsClientStream = TcpClientStream>>>; /// A Builder for the TlsClientStream pub struct TlsClientStreamBuilder(TlsStreamBuilder); impl TlsClientStreamBuilder { /// Creates a builder for the construction of a TlsClientStream. pub fn new() -> Self { Self(TlsStreamBuilder::new()) } /// Add a custom trusted peer certificate or certificate authority. /// /// If this is the 'client' then the 'server' must have it associated as it's `identity`, or have had the `identity` signed by this certificate. pub fn add_ca(&mut self, ca: X509) { self.0.add_ca(ca); } /// Add a custom trusted peer certificate or certificate authority encoded as a (binary) DER-encoded X.509 certificate. /// /// If this is the 'client' then the 'server' must have it associated as it's `identity`, or have had the `identity` signed by this certificate. pub fn add_ca_der(&mut self, ca_der: &[u8]) -> io::Result<()> { let ca = X509::from_der(ca_der) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; self.add_ca(ca); Ok(()) } /// Client side identity for client auth in TLS (aka mutual TLS auth) #[cfg(feature = "mtls")] pub fn identity(&mut self, pkcs12: Pkcs12) { self.0.identity(pkcs12); } /// Sets the address to connect from. pub fn bind_addr(&mut self, bind_addr: SocketAddr) { self.0.bind_addr(bind_addr); } /// Creates a new TlsStream to the specified name_server with future /// /// # Arguments /// /// * `future` - future for underlying tcp stream /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate #[allow(clippy::type_complexity)] pub fn build_with_future( self, future: F, name_server: SocketAddr, dns_name: String, ) -> ( Pin, ProtoError>> + Send>>, BufDnsStreamHandle, ) where F: Future> + Send + Unpin + 'static, { let (stream_future, sender) = self.0.build_with_future(future, name_server, dns_name); let new_future = Box::pin( stream_future .map_ok(TcpClientStream::from_stream) .map_err(ProtoError::from), ); (new_future, sender) } } impl Default for TlsClientStreamBuilder { fn default() -> Self { Self::new() } } impl TlsClientStreamBuilder { /// Creates a new TlsStream to the specified name_server /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `bind_addr` - IP and port to connect from /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate #[allow(clippy::type_complexity)] pub fn build( self, name_server: SocketAddr, dns_name: String, ) -> ( Pin, ProtoError>> + Send>>, BufDnsStreamHandle, ) { let (stream_future, sender) = self.0.build(name_server, dns_name); let new_future = Box::pin( stream_future .map_ok(TcpClientStream::from_stream) .map_err(ProtoError::from), ); (new_future, sender) } } hickory-proto-0.24.0/src/openssl/tls_server.rs000064400000000000000000000114771046102023000175420ustar 00000000000000// Copyright 2015-2021 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! DNS over TLS server implementations for OpenSSL use std::fs::File; use std::io; use std::io::Read; use std::path::Path; use crate::error::{ProtoError, ProtoResult}; use openssl::ssl::{SslAcceptor, SslMethod, SslOptions, SslVerifyMode}; pub use openssl::pkcs12::Pkcs12; pub use openssl::pkey::{PKey, Private}; pub use openssl::stack::Stack; pub use openssl::x509::X509; /// Read the certificate from the specified path. /// /// If the password is specified, then it will be used to decode the Certificate #[allow(clippy::type_complexity)] pub fn read_cert_pkcs12( path: &Path, password: Option<&str>, ) -> ProtoResult<((Option, Option>), Option>)> { let mut file = File::open(path).map_err(|e| { ProtoError::from(format!( "error opening pkcs12 cert file: {}: {}", path.display(), e )) })?; let mut pkcs12_bytes = vec![]; file.read_to_end(&mut pkcs12_bytes).map_err(|e| { ProtoError::from(format!( "could not read pkcs12 from: {}: {}", path.display(), e )) })?; let pkcs12 = Pkcs12::from_der(&pkcs12_bytes).map_err(|e| { ProtoError::from(format!( "badly formatted pkcs12 from: {}: {}", path.display(), e )) })?; let parsed = pkcs12.parse2(password.unwrap_or("")).map_err(|e| { ProtoError::from(format!( "failed to open pkcs12 from: {}: {}", path.display(), e )) })?; Ok(((parsed.cert, parsed.ca), parsed.pkey)) } /// Read the certificate from the specified path. /// /// If the password is specified, then it will be used to decode the Certificate pub fn read_cert_pem(path: &Path) -> ProtoResult<(X509, Option>)> { let mut file = File::open(path).map_err(|e| { ProtoError::from(format!( "error opening cert file: {}: {}", path.display(), e )) })?; let mut key_bytes = vec![]; file.read_to_end(&mut key_bytes).map_err(|e| { ProtoError::from(format!( "could not read cert key from: {}: {}", path.display(), e )) })?; let cert_chain = X509::stack_from_pem(&key_bytes)?; let cert_count = cert_chain.len(); let mut iter = cert_chain.into_iter(); let cert = match iter.next() { None => { return Err(ProtoError::from(format!( "no certs read from file: {}", path.display() ))) } Some(cert) => cert, }; if cert_count < 1 { Ok((cert, None)) } else { let mut stack = Stack::::new()?; for c in iter { stack.push(c)?; } Ok((cert, Some(stack))) } } /// Reads a private key from a pkcs8 formatted, and possibly encoded file pub fn read_key_from_pkcs8(path: &Path, password: Option<&str>) -> ProtoResult> { let mut file = File::open(path)?; let mut buf = Vec::new(); file.read_to_end(&mut buf)?; match password.map(str::as_bytes) { Some(password) => { PKey::private_key_from_pkcs8_passphrase(&buf, password).map_err(Into::into) } None => PKey::private_key_from_pkcs8_passphrase(&buf, &[0_u8; 0]).map_err(Into::into), } } /// Reads a private key from a der formatted file pub fn read_key_from_der(path: &Path) -> ProtoResult> { let mut file = File::open(path)?; let mut buf = Vec::new(); file.read_to_end(&mut buf)?; PKey::private_key_from_der(&buf).map_err(Into::into) } /// Construct the new Acceptor with the associated pkcs12 data pub fn new_acceptor( cert: X509, chain: Option>, key: PKey, ) -> io::Result { // TODO: make an internal error type with conversions let mut builder = SslAcceptor::mozilla_modern(SslMethod::tls())?; builder.set_private_key(&key)?; builder.set_certificate(&cert)?; builder.set_verify(SslVerifyMode::NONE); builder.set_options( SslOptions::NO_COMPRESSION | SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1, ); if let Some(ref chain) = chain { for cert in chain { builder.add_extra_chain_cert(cert.to_owned())?; } } // validate our certificate and private key match builder.check_private_key()?; Ok(builder.build()) } hickory-proto-0.24.0/src/openssl/tls_stream.rs000064400000000000000000000226431046102023000175240ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::io; use std::net::SocketAddr; use std::pin::Pin; use std::{future::Future, marker::PhantomData}; use futures_util::{future, TryFutureExt}; use openssl::pkcs12::ParsedPkcs12_2; use openssl::pkey::{PKey, Private}; use openssl::ssl::{ConnectConfiguration, SslConnector, SslContextBuilder, SslMethod, SslOptions}; use openssl::stack::Stack; use openssl::x509::store::X509StoreBuilder; use openssl::x509::X509; use tokio_openssl::{self, SslStream as TokioTlsStream}; use crate::iocompat::{AsyncIoStdAsTokio, AsyncIoTokioAsStd}; use crate::tcp::TcpStream; use crate::tcp::{Connect, DnsTcpStream}; use crate::xfer::BufDnsStreamHandle; pub(crate) trait TlsIdentityExt { fn identity(&mut self, pkcs12: &ParsedPkcs12_2) -> io::Result<()> { self.identity_parts( pkcs12.cert.as_ref(), pkcs12.pkey.as_ref(), pkcs12.ca.as_ref(), ) } fn identity_parts( &mut self, cert: Option<&X509>, pkey: Option<&PKey>, chain: Option<&Stack>, ) -> io::Result<()>; } impl TlsIdentityExt for SslContextBuilder { fn identity_parts( &mut self, cert: Option<&X509>, pkey: Option<&PKey>, chain: Option<&Stack>, ) -> io::Result<()> { if let Some(cert) = cert { self.set_certificate(cert)?; } if let Some(pkey) = pkey { self.set_private_key(pkey)?; } self.check_private_key()?; if let Some(chain) = chain { for cert in chain { self.add_extra_chain_cert(cert.to_owned())?; } } Ok(()) } } /// A TlsStream counterpart to the TcpStream which embeds a secure TlsStream pub type TlsStream = TcpStream>>; pub(crate) type CompatTlsStream = TlsStream>; fn new(certs: Vec, pkcs12: Option) -> io::Result { let mut tls = SslConnector::builder(SslMethod::tls()) .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}")))?; // mutable reference block { let openssl_ctx_builder = &mut tls; // only want to support current TLS versions, 1.2 or future openssl_ctx_builder.set_options( SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1, ); let mut store = X509StoreBuilder::new().map_err(|e| { io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}")) })?; for cert in certs { store.add_cert(cert).map_err(|e| { io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}")) })?; } openssl_ctx_builder .set_verify_cert_store(store.build()) .map_err(|e| { io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}")) })?; // if there was a pkcs12 associated, we'll add it to the identity if let Some(pkcs12) = pkcs12 { openssl_ctx_builder.identity(&pkcs12)?; } } Ok(tls.build()) } /// Initializes a TlsStream with an existing tokio_tls::TlsStream. /// /// This is intended for use with a TlsListener and Incoming connections pub fn tls_stream_from_existing_tls_stream( stream: AsyncIoTokioAsStd>>, peer_addr: SocketAddr, ) -> (CompatTlsStream, BufDnsStreamHandle) { let (message_sender, outbound_messages) = BufDnsStreamHandle::new(peer_addr); let stream = TcpStream::from_stream_with_receiver(stream, peer_addr, outbound_messages); (stream, message_sender) } async fn connect_tls( future: F, tls_config: ConnectConfiguration, dns_name: String, ) -> Result>, io::Error> where S: DnsTcpStream, F: Future> + Send + Unpin + 'static, { let tcp = future .await .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}")))?; let mut stream = tls_config .into_ssl(&dns_name) .and_then(|ssl| TokioTlsStream::new(ssl, AsyncIoStdAsTokio(tcp))) .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("tls error: {e}")))?; Pin::new(&mut stream) .connect() .await .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}")))?; Ok(stream) } /// A builder for the TlsStream #[derive(Default)] pub struct TlsStreamBuilder { ca_chain: Vec, identity: Option, bind_addr: Option, marker: PhantomData, } impl TlsStreamBuilder { /// A builder for associating trust information to the `TlsStream`. pub fn new() -> Self { Self { ca_chain: vec![], identity: None, bind_addr: None, marker: PhantomData, } } /// Add a custom trusted peer certificate or certificate authority. /// /// If this is the 'client' then the 'server' must have it associated as it's `identity`, or have had the `identity` signed by this pub fn add_ca(&mut self, ca: X509) { self.ca_chain.push(ca); } /// Client side identity for client auth in TLS (aka mutual TLS auth) #[cfg(feature = "mtls")] pub fn identity(&mut self, pkcs12: ParsedPkcs12) { self.identity = Some(pkcs12); } /// Sets the address to connect from. pub fn bind_addr(&mut self, bind_addr: SocketAddr) { self.bind_addr = Some(bind_addr); } /// Similar to `build`, but with prebuilt tcp stream #[allow(clippy::type_complexity)] pub fn build_with_future( self, future: F, name_server: SocketAddr, dns_name: String, ) -> ( Pin, io::Error>> + Send>>, BufDnsStreamHandle, ) where F: Future> + Send + Unpin + 'static, { let (message_sender, outbound_messages) = BufDnsStreamHandle::new(name_server); let tls_config = match new(self.ca_chain, self.identity) { Ok(c) => c, Err(e) => { return ( Box::pin(future::err(e).map_err(|e| { io::Error::new(io::ErrorKind::ConnectionRefused, format!("tls error: {e}")) })), message_sender, ) } }; let tls_config = match tls_config.configure() { Ok(c) => c, Err(e) => { return ( Box::pin(future::err(e).map_err(|e| { io::Error::new( io::ErrorKind::ConnectionRefused, format!("tls config error: {e}"), ) })), message_sender, ) } }; // This set of futures collapses the next tcp socket into a stream which can be used for // sending and receiving tcp packets. let stream = Box::pin(connect_tls(future, tls_config, dns_name).map_ok(move |s| { TcpStream::from_stream_with_receiver( AsyncIoTokioAsStd(s), name_server, outbound_messages, ) })); (stream, message_sender) } } impl TlsStreamBuilder { /// Creates a new TlsStream to the specified name_server /// /// [RFC 7858](https://tools.ietf.org/html/rfc7858), DNS over TLS, May 2016 /// /// ```text /// 3.2. TLS Handshake and Authentication /// /// Once the DNS client succeeds in connecting via TCP on the well-known /// port for DNS over TLS, it proceeds with the TLS handshake [RFC5246], /// following the best practices specified in [BCP195]. /// /// The client will then authenticate the server, if required. This /// document does not propose new ideas for authentication. Depending on /// the privacy profile in use (Section 4), the DNS client may choose not /// to require authentication of the server, or it may make use of a /// trusted Subject Public Key Info (SPKI) Fingerprint pin set. /// /// After TLS negotiation completes, the connection will be encrypted and /// is now protected from eavesdropping. /// ``` /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate #[allow(clippy::type_complexity)] pub fn build( self, name_server: SocketAddr, dns_name: String, ) -> ( Pin, io::Error>> + Send>>, BufDnsStreamHandle, ) { let future = S::connect_with_bind(name_server, self.bind_addr); self.build_with_future(future, name_server, dns_name) } } hickory-proto-0.24.0/src/quic/mod.rs000064400000000000000000000015121046102023000153740ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! QUIC protocol related components for DNS over QUIC (DoQ) mod quic_client_stream; mod quic_config; mod quic_server; pub(crate) mod quic_socket; mod quic_stream; pub use self::quic_client_stream::{ client_config_tls13, QuicClientConnect, QuicClientResponse, QuicClientStream, QuicClientStreamBuilder, }; pub use self::quic_server::{QuicServer, QuicStreams}; pub use self::quic_stream::{DoqErrorCode, QuicStream}; pub use crate::udp::QuicLocalAddr; #[cfg(test)] mod tests; hickory-proto-0.24.0/src/quic/quic_client_stream.rs000064400000000000000000000330671046102023000205010ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::{ fmt::{self, Display}, future::Future, net::SocketAddr, pin::Pin, sync::Arc, task::{Context, Poll}, }; use futures_util::{future::FutureExt, stream::Stream}; use quinn::{ClientConfig, Connection, Endpoint, TransportConfig, VarInt}; use rustls::{version::TLS13, ClientConfig as TlsClientConfig}; use crate::udp::{DnsUdpSocket, QuicLocalAddr}; use crate::{ error::ProtoError, quic::quic_socket::QuinnAsyncUdpSocketAdapter, quic::quic_stream::{DoqErrorCode, QuicStream}, udp::UdpSocket, xfer::{DnsRequest, DnsRequestSender, DnsResponse, DnsResponseStream}, }; use super::{quic_config, quic_stream}; /// A DNS client connection for DNS-over-QUIC #[must_use = "futures do nothing unless polled"] pub struct QuicClientStream { quic_connection: Connection, name_server_name: Arc, name_server: SocketAddr, is_shutdown: bool, } impl Display for QuicClientStream { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( formatter, "QUIC({},{})", self.name_server, self.name_server_name ) } } impl QuicClientStream { /// Builder for QuicClientStream pub fn builder() -> QuicClientStreamBuilder { QuicClientStreamBuilder::default() } async fn inner_send( connection: Connection, message: DnsRequest, ) -> Result { let (send_stream, recv_stream) = connection.open_bi().await?; // RFC: The mapping specified here requires that the client selects a separate // QUIC stream for each query. The server then uses the same stream to provide all the response messages for that query. let mut stream = QuicStream::new(send_stream, recv_stream); stream.send(message.into_parts().0).await?; // The client MUST send the DNS query over the selected stream, // and MUST indicate through the STREAM FIN mechanism that no further data will be sent on that stream. stream.finish().await?; stream.receive().await } } impl DnsRequestSender for QuicClientStream { /// The send loop for QUIC in DNS stipulates that a new QUIC "stream" should be opened and use for sending data. /// /// It should be closed after receiving the response. TODO: AXFR/IXFR support... /// /// ```text /// 5.2. Stream Mapping and Usage /// /// The mapping of DNS traffic over QUIC streams takes advantage of the QUIC stream features detailed in Section 2 of [RFC9000], /// the QUIC transport specification. /// /// DNS traffic follows a simple pattern in which the client sends a query, and the server provides one or more responses /// (multiple responses can occur in zone transfers).The mapping specified here requires that the client selects a separate /// QUIC stream for each query. The server then uses the same stream to provide all the response messages for that query. In /// order that multiple responses can be parsed, a 2-octet length field is used in exactly the same way as the 2-octet length /// field defined for DNS over TCP [RFC1035]. The practical result of this is that the content of each QUIC stream is exactly /// the same as the content of a TCP connection that would manage exactly one query.All DNS messages (queries and responses) /// sent over DoQ connections MUST be encoded as a 2-octet length field followed by the message content as specified in [RFC1035]. /// The client MUST select the next available client-initiated bidirectional stream for each subsequent query on a QUIC connection, /// in conformance with the QUIC transport specification [RFC9000].The client MUST send the DNS query over the selected stream, /// and MUST indicate through the STREAM FIN mechanism that no further data will be sent on that stream.The server MUST send the /// response(s) on the same stream and MUST indicate, after the last response, through the STREAM FIN mechanism that no further /// data will be sent on that stream.Therefore, a single DNS transaction consumes a single bidirectional client-initiated stream. /// This means that the client's first query occurs on QUIC stream 0, the second on 4, and so on (see Section 2.1 of [RFC9000]. /// Servers MAY defer processing of a query until the STREAM FIN has been indicated on the stream selected by the client. Servers /// and clients MAY monitor the number of "dangling" streams for which the expected queries or responses have been received but /// not the STREAM FIN. Implementations MAY impose a limit on the number of such dangling streams. If limits are encountered, /// implementations MAY close the connection. /// /// 5.2.1. DNS Message IDs /// /// When sending queries over a QUIC connection, the DNS Message ID MUST be set to zero. The stream mapping for DoQ allows for /// unambiguous correlation of queries and responses and so the Message ID field is not required. /// /// This has implications for proxying DoQ message to and from other transports. For example, proxies may have to manage the /// fact that DoQ can support a larger number of outstanding queries on a single connection than e.g., DNS over TCP because DoQ /// is not limited by the Message ID space. This issue already exists for DoH, where a Message ID of 0 is recommended.When forwarding /// a DNS message from DoQ over another transport, a DNS Message ID MUST be generated according to the rules of the protocol that is /// in use. When forwarding a DNS message from another transport over DoQ, the Message ID MUST be set to zero. /// ``` fn send_message(&mut self, message: DnsRequest) -> DnsResponseStream { if self.is_shutdown { panic!("can not send messages after stream is shutdown") } Box::pin(Self::inner_send(self.quic_connection.clone(), message)).into() } fn shutdown(&mut self) { self.is_shutdown = true; self.quic_connection .close(DoqErrorCode::NoError.into(), b"Shutdown"); } fn is_shutdown(&self) -> bool { self.is_shutdown } } impl Stream for QuicClientStream { type Item = Result<(), ProtoError>; fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { if self.is_shutdown { Poll::Ready(None) } else { Poll::Ready(Some(Ok(()))) } } } /// A QUIC connection builder for DNS-over-QUIC #[derive(Clone)] pub struct QuicClientStreamBuilder { crypto_config: Option, transport_config: Arc, bind_addr: Option, } impl QuicClientStreamBuilder { /// Constructs a new TlsStreamBuilder with the associated ClientConfig pub fn crypto_config(&mut self, crypto_config: TlsClientConfig) -> &mut Self { self.crypto_config = Some(crypto_config); self } /// Sets the address to connect from. pub fn bind_addr(&mut self, bind_addr: SocketAddr) -> &mut Self { self.bind_addr = Some(bind_addr); self } /// Creates a new QuicStream to the specified name_server /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate pub fn build(self, name_server: SocketAddr, dns_name: String) -> QuicClientConnect { QuicClientConnect(Box::pin(self.connect(name_server, dns_name)) as _) } /// Create a QuicStream with existing connection pub fn build_with_future( self, future: F, name_server: SocketAddr, dns_name: String, ) -> QuicClientConnect where S: DnsUdpSocket + QuicLocalAddr + 'static, F: Future> + Send + 'static, { QuicClientConnect(Box::pin(self.connect_with_future(future, name_server, dns_name)) as _) } async fn connect_with_future( self, future: F, name_server: SocketAddr, dns_name: String, ) -> Result where S: DnsUdpSocket + QuicLocalAddr + 'static, F: Future> + Send, { let socket = future.await?; let endpoint_config = quic_config::endpoint(); let wrapper = QuinnAsyncUdpSocketAdapter { io: socket }; let endpoint = Endpoint::new_with_abstract_socket( endpoint_config, None, wrapper, Arc::new(quinn::TokioRuntime), )?; self.connect_inner(endpoint, name_server, dns_name).await } async fn connect( self, name_server: SocketAddr, dns_name: String, ) -> Result { let connect = if let Some(bind_addr) = self.bind_addr { ::connect_with_bind(name_server, bind_addr) } else { ::connect(name_server) }; let socket = connect.await?; let socket = socket.into_std()?; let endpoint_config = quic_config::endpoint(); let endpoint = Endpoint::new(endpoint_config, None, socket, Arc::new(quinn::TokioRuntime))?; self.connect_inner(endpoint, name_server, dns_name).await } async fn connect_inner( self, mut endpoint: Endpoint, name_server: SocketAddr, dns_name: String, ) -> Result { // ensure the ALPN protocol is set correctly let mut crypto_config = if let Some(crypto_config) = self.crypto_config { crypto_config } else { client_config_tls13()? }; if crypto_config.alpn_protocols.is_empty() { crypto_config.alpn_protocols = vec![quic_stream::DOQ_ALPN.to_vec()]; } let early_data_enabled = crypto_config.enable_early_data; let mut client_config = ClientConfig::new(Arc::new(crypto_config)); client_config.transport_config(self.transport_config.clone()); endpoint.set_default_client_config(client_config); let connecting = endpoint.connect(name_server, &dns_name)?; // TODO: for Client/Dynamic update, don't use RTT, for queries, do use it. let quic_connection = if early_data_enabled { match connecting.into_0rtt() { Ok((new_connection, _)) => new_connection, Err(connecting) => connecting.await?, } } else { connecting.await? }; Ok(QuicClientStream { quic_connection, name_server_name: Arc::from(dns_name), name_server, is_shutdown: false, }) } } /// Default crypto options for quic pub fn client_config_tls13() -> Result { use rustls::RootCertStore; #[cfg_attr( not(any(feature = "native-certs", feature = "webpki-roots")), allow(unused_mut) )] let mut root_store = RootCertStore::empty(); #[cfg(all(feature = "native-certs", not(feature = "webpki-roots")))] { use crate::error::ProtoErrorKind; let (added, ignored) = root_store.add_parsable_certificates(&rustls_native_certs::load_native_certs()?); if ignored > 0 { tracing::warn!( "failed to parse {} certificate(s) from the native root store", ignored, ); } if added == 0 { return Err(ProtoErrorKind::NativeCerts.into()); } } #[cfg(feature = "webpki-roots")] root_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( ta.subject, ta.spki, ta.name_constraints, ) })); Ok(TlsClientConfig::builder() .with_safe_default_cipher_suites() .with_safe_default_kx_groups() .with_protocol_versions(&[&TLS13]) .expect("TLS 1.3 not supported") .with_root_certificates(root_store) .with_no_client_auth()) } impl Default for QuicClientStreamBuilder { fn default() -> Self { let mut transport_config = quic_config::transport(); // clients never accept new bidirectional streams transport_config.max_concurrent_bidi_streams(VarInt::from_u32(0)); Self { crypto_config: None, transport_config: Arc::new(transport_config), bind_addr: None, } } } /// A future that resolves to an QuicClientStream pub struct QuicClientConnect( Pin> + Send>>, ); impl Future for QuicClientConnect { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.0.poll_unpin(cx) } } /// A future that resolves to pub struct QuicClientResponse( Pin> + Send>>, ); impl Future for QuicClientResponse { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.0.as_mut().poll(cx).map_err(ProtoError::from) } } hickory-proto-0.24.0/src/quic/quic_config.rs000064400000000000000000000024271046102023000171110ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use quinn::{EndpointConfig, TransportConfig, VarInt}; /// Returns a default endpoint configuration for DNS-over-QUIC pub(crate) fn endpoint() -> EndpointConfig { // set some better EndpointConfig defaults for DoQ let mut endpoint_config = EndpointConfig::default(); // all DNS packets have a maximum size of u16 due to DoQ and 1035 rfc // TODO: the RFC claims max == u16::max, but this matches the max in some test servers. endpoint_config .max_udp_payload_size(0x45ac) .expect("max udp payload size exceeded"); endpoint_config } /// Returns a default endpoint configuration for DNS-over-QUIC pub(crate) fn transport() -> TransportConfig { let mut transport_config = TransportConfig::default(); transport_config.max_concurrent_uni_streams(VarInt::from_u32(0)); transport_config.datagram_receive_buffer_size(None); transport_config.datagram_send_buffer_size(0); transport_config } hickory-proto-0.24.0/src/quic/quic_server.rs000064400000000000000000000072121046102023000171470ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::{io, net::SocketAddr, sync::Arc}; use quinn::{Connection, Endpoint, ServerConfig}; use rustls::{server::ServerConfig as TlsServerConfig, version::TLS13, Certificate, PrivateKey}; use crate::{error::ProtoError, udp::UdpSocket}; use super::{ quic_config, quic_stream::{self, QuicStream}, }; /// A DNS-over-QUIC Server, see QuicClientStream for the client counterpart pub struct QuicServer { endpoint: Endpoint, } impl QuicServer { /// Construct the new Acceptor with the associated pkcs12 data pub async fn new( name_server: SocketAddr, cert: Vec, key: PrivateKey, ) -> Result { // setup a new socket for the server to use let socket = ::bind(name_server).await?; Self::with_socket(socket, cert, key) } /// Construct the new server with an existing socket pub fn with_socket( socket: tokio::net::UdpSocket, cert: Vec, key: PrivateKey, ) -> Result { let mut config = TlsServerConfig::builder() .with_safe_default_cipher_suites() .with_safe_default_kx_groups() .with_protocol_versions(&[&TLS13]) .expect("TLS1.3 not supported") .with_no_client_auth() .with_single_cert(cert, key)?; config.alpn_protocols = vec![quic_stream::DOQ_ALPN.to_vec()]; let mut server_config = ServerConfig::with_crypto(Arc::new(config)); server_config.transport = Arc::new(quic_config::transport()); let socket = socket.into_std()?; let endpoint_config = quic_config::endpoint(); let endpoint = Endpoint::new( endpoint_config, Some(server_config), socket, Arc::new(quinn::TokioRuntime), )?; Ok(Self { endpoint }) } /// Get the next incoming stream /// /// # Returns /// /// A remote connection that could have many potential bi-directional streams and the remote socket address pub async fn next(&mut self) -> Result, ProtoError> { let connecting = match self.endpoint.accept().await { Some(conn) => conn, None => return Ok(None), }; let remote_addr = connecting.remote_address(); let connection = connecting.await?; Ok(Some((QuicStreams { connection }, remote_addr))) } /// Returns the address this server is listening on /// /// This can be useful in tests, where a random port can be associated with the server by binding on `127.0.0.1:0` and then getting the /// associated port address with this function. pub fn local_addr(&self) -> Result { self.endpoint.local_addr() } } /// A stream of bi-directional QUIC streams pub struct QuicStreams { connection: Connection, } impl QuicStreams { /// Get the next bi directional stream from the client pub async fn next(&mut self) -> Option> { match self.connection.accept_bi().await { Ok((send_stream, receive_stream)) => { Some(Ok(QuicStream::new(send_stream, receive_stream))) } Err(e) => Some(Err(e.into())), } } } hickory-proto-0.24.0/src/quic/quic_socket.rs000064400000000000000000000101201046102023000171210ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::fmt::{Debug, Formatter}; use std::{ fmt, task::{Context, Poll}, }; use quinn::AsyncUdpSocket; use crate::udp::{DnsUdpSocket, QuicLocalAddr}; /// Wrapper used for quinn::Endpoint::new_with_abstract_socket pub(crate) struct QuinnAsyncUdpSocketAdapter { pub(crate) io: S, } impl Debug for QuinnAsyncUdpSocketAdapter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("Wrapper for quinn::AsyncUdpSocket") } } /// TODO: Naive implementation. Look forward to future improvements. impl AsyncUdpSocket for QuinnAsyncUdpSocketAdapter { fn poll_send( &self, _state: &quinn::udp::UdpState, cx: &mut Context<'_>, transmits: &[quinn::udp::Transmit], ) -> Poll> { // logics from quinn-udp::fallback.rs let io = &self.io; let mut sent = 0; for transmit in transmits { match io.poll_send_to(cx, &transmit.contents, transmit.destination) { Poll::Ready(ready) => match ready { Ok(_) => { sent += 1; } // We need to report that some packets were sent in this case, so we rely on // errors being either harmlessly transient (in the case of WouldBlock) or // recurring on the next call. Err(_) if sent != 0 => return Poll::Ready(Ok(sent)), Err(e) => { if e.kind() == std::io::ErrorKind::WouldBlock { return Poll::Ready(Err(e)); } // Other errors are ignored, since they will ususally be handled // by higher level retransmits and timeouts. // - PermissionDenied errors have been observed due to iptable rules. // Those are not fatal errors, since the // configuration can be dynamically changed. // - Destination unreachable errors have been observed for other // log_sendmsg_error(&mut self.last_send_error, e, transmit); sent += 1; } }, Poll::Pending => { return if sent == 0 { Poll::Pending } else { Poll::Ready(Ok(sent)) } } } } Poll::Ready(Ok(sent)) } fn poll_recv( &self, cx: &mut Context<'_>, bufs: &mut [std::io::IoSliceMut<'_>], meta: &mut [quinn::udp::RecvMeta], ) -> Poll> { // logics from quinn-udp::fallback.rs let io = &self.io; let Some(buf) = bufs.get_mut(0) else { return Poll::Ready(Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, "no buf", ))); }; match io.poll_recv_from(cx, buf.as_mut()) { Poll::Ready(res) => match res { Ok((len, addr)) => { meta[0] = quinn::udp::RecvMeta { len, stride: len, addr, ecn: None, dst_ip: None, }; Poll::Ready(Ok(1)) } Err(err) => Poll::Ready(Err(err)), }, Poll::Pending => Poll::Pending, } } fn local_addr(&self) -> std::io::Result { self.io.local_addr() } } hickory-proto-0.24.0/src/quic/quic_stream.rs000064400000000000000000000214371046102023000171410ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use bytes::{Bytes, BytesMut}; use quinn::{RecvStream, SendStream, VarInt}; use tracing::debug; use crate::{ error::{ProtoError, ProtoErrorKind}, op::Message, xfer::DnsResponse, }; /// ```text /// 5.1. Connection Establishment /// /// DoQ connections are established as described in the QUIC transport specification [RFC9000]. During connection establishment, /// DoQ support is indicated by selecting the ALPN token "doq" in the crypto handshake. /// ``` pub(crate) const DOQ_ALPN: &[u8] = b"doq"; /// [DoQ Error Codes](https://www.ietf.org/archive/id/draft-ietf-dprive-dnsoquic-10.html#name-doq-error-codes), draft-ietf-dprive-dnsoquic, Feb. 28, 2022 /// ```text /// 5.3. DoQ Error Codes /// /// The following error codes are defined for use when abruptly terminating streams, aborting reading of streams, or immediately closing connections: /// /// DOQ_NO_ERROR (0x0): /// No error. This is used when the connection or stream needs to be closed, but there is no error to signal. /// /// DOQ_INTERNAL_ERROR (0x1): /// The DoQ implementation encountered an internal error and is incapable of pursuing the transaction or the connection. /// /// DOQ_PROTOCOL_ERROR (0x2): /// The DoQ implementation encountered an protocol error and is forcibly aborting the connection. /// /// DOQ_REQUEST_CANCELLED (0x3): /// A DoQ client uses this to signal that it wants to cancel an outstanding transaction. /// /// DOQ_EXCESSIVE_LOAD (0x4): /// A DoQ implementation uses this to signal when closing a connection due to excessive load. /// /// DOQ_ERROR_RESERVED (0xd098ea5e): /// Alternative error code used for tests. /// ``` #[derive(Clone, Copy)] pub enum DoqErrorCode { /// No error. This is used when the connection or stream needs to be closed, but there is no error to signal. NoError, /// The DoQ implementation encountered an internal error and is incapable of pursuing the transaction or the connection. InternalError, /// The DoQ implementation encountered an protocol error and is forcibly aborting the connection. ProtocolError, /// A DoQ client uses this to signal that it wants to cancel an outstanding transaction. RequestCancelled, /// A DoQ implementation uses this to signal when closing a connection due to excessive load. ExcessiveLoad, /// Alternative error code used for tests. ErrorReserved, /// Unknown Error code Unknown(u32), } // not using repr(u32) above because of the Unknown const NO_ERROR: u32 = 0x0; const INTERNAL_ERROR: u32 = 0x1; const PROTOCOL_ERROR: u32 = 0x2; const REQUEST_CANCELLED: u32 = 0x3; const EXCESSIVE_LOAD: u32 = 0x4; const ERROR_RESERVED: u32 = 0xd098ea5e; impl From for VarInt { fn from(doq_error: DoqErrorCode) -> Self { use DoqErrorCode::*; match doq_error { NoError => Self::from_u32(NO_ERROR), InternalError => Self::from_u32(INTERNAL_ERROR), ProtocolError => Self::from_u32(PROTOCOL_ERROR), RequestCancelled => Self::from_u32(REQUEST_CANCELLED), ExcessiveLoad => Self::from_u32(EXCESSIVE_LOAD), ErrorReserved => Self::from_u32(ERROR_RESERVED), Unknown(code) => Self::from_u32(code), } } } impl From for DoqErrorCode { fn from(doq_error: VarInt) -> Self { let code: u32 = if let Ok(code) = doq_error.into_inner().try_into() { code } else { return Self::ProtocolError; }; match code { NO_ERROR => Self::NoError, INTERNAL_ERROR => Self::InternalError, PROTOCOL_ERROR => Self::ProtocolError, REQUEST_CANCELLED => Self::RequestCancelled, EXCESSIVE_LOAD => Self::ExcessiveLoad, ERROR_RESERVED => Self::ErrorReserved, _ => Self::Unknown(code), } } } /// A single bi-directional stream pub struct QuicStream { send_stream: SendStream, receive_stream: RecvStream, } impl QuicStream { pub(crate) fn new(send_stream: SendStream, receive_stream: RecvStream) -> Self { Self { send_stream, receive_stream, } } /// Send the DNS message to the other side pub async fn send(&mut self, mut message: Message) -> Result<(), ProtoError> { // RFC: When sending queries over a QUIC connection, the DNS Message ID MUST be set to zero. The stream mapping for DoQ allows for // unambiguous correlation of queries and responses and so the Message ID field is not required. message.set_id(0); let bytes = Bytes::from(message.to_vec()?); self.send_bytes(bytes).await } /// Send pre-encoded bytes, warning, QUIC requires the message id to be 0. pub async fn send_bytes(&mut self, bytes: Bytes) -> Result<(), ProtoError> { // In order that multiple responses can be parsed, a 2-octet length field is used in exactly the same way as the 2-octet length // field defined for DNS over TCP [RFC1035]. The practical result of this is that the content of each QUIC stream is exactly // the same as the content of a TCP connection that would manage exactly one query.All DNS messages (queries and responses) // sent over DoQ connections MUST be encoded as a 2-octet length field followed by the message content as specified in [RFC1035]. let bytes_len = u16::try_from(bytes.len()) .map_err(|_e| ProtoErrorKind::MaxBufferSizeExceeded(bytes.len()))?; let len = bytes_len.to_be_bytes().to_vec(); let len = Bytes::from(len); debug!("received packet len: {} bytes: {:x?}", bytes_len, bytes); self.send_stream.write_all_chunks(&mut [len, bytes]).await?; Ok(()) } /// finishes the send stream, i.e. there will be no more data sent to the remote pub async fn finish(&mut self) -> Result<(), ProtoError> { self.send_stream.finish().await?; Ok(()) } /// Receive a single packet pub async fn receive(&mut self) -> Result { let bytes = self.receive_bytes().await?; let message = Message::from_vec(&bytes)?; // assert that the message id is 0, this is a bad dns-over-quic packet if not if message.id() != 0 { self.reset(DoqErrorCode::ProtocolError) .map_err(|_| debug!("stream already closed")) .ok(); return Err(ProtoErrorKind::QuicMessageIdNot0(message.id()).into()); } Ok(DnsResponse::new(message, bytes.to_vec())) } // TODO: we should change the protocol handlers to work with Messages since some require things like 0 for the Message ID. /// Receive a single packet as raw bytes pub async fn receive_bytes(&mut self) -> Result { // following above, the data should be first the length, followed by the message(s) let mut len = [0u8; 2]; self.receive_stream.read_exact(&mut len).await?; let len = u16::from_be_bytes(len) as usize; // RFC: DoQ Queries and Responses are sent on QUIC streams, which in theory can carry up to 2^62 bytes. // However, DNS messages are restricted in practice to a maximum size of 65535 bytes. This maximum size // is enforced by the use of a two-octet message length field in DNS over TCP [RFC1035] and DNS over TLS [RFC7858], // and by the definition of the "application/dns-message" for DNS over HTTP [RFC8484]. DoQ enforces the same restriction. let mut bytes = BytesMut::with_capacity(len); bytes.resize(len, 0); if let Err(e) = self.receive_stream.read_exact(&mut bytes[..len]).await { debug!("received bad packet len: {} bytes: {:?}", len, bytes); self.reset(DoqErrorCode::ProtocolError) .map_err(|_| debug!("stream already closed")) .ok(); return Err(e.into()); } debug!("received packet len: {} bytes: {:x?}", len, bytes); Ok(bytes) } /// Reset the sending stream due to some error pub fn reset(&mut self, code: DoqErrorCode) -> Result<(), ProtoError> { self.send_stream .reset(code.into()) .map_err(|_| ProtoError::from(ProtoErrorKind::QuinnUnknownStreamError)) } /// Stop the receiving stream due to some error pub fn stop(&mut self, code: DoqErrorCode) -> Result<(), ProtoError> { self.receive_stream .stop(code.into()) .map_err(|_| ProtoError::from(ProtoErrorKind::QuinnUnknownStreamError)) } } hickory-proto-0.24.0/src/quic/tests.rs000064400000000000000000000076531046102023000157730ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. #![allow(clippy::print_stdout)] // this is a test module use std::{env, net::SocketAddr, path::Path, str::FromStr, sync::Arc}; use futures_util::StreamExt; use rustls::{ClientConfig, KeyLogFile}; use crate::{ op::{Message, Query}, quic::QuicClientStreamBuilder, rr::{Name, RecordType}, rustls::tls_server, xfer::DnsRequestSender, }; use super::quic_server::QuicServer; async fn server_responder(mut server: QuicServer) { while let Some((mut conn, addr)) = server .next() .await .expect("failed to get next quic session") { println!("received client request {addr}"); while let Some(stream) = conn.next().await { let mut stream = stream.expect("new client stream failed"); let client_message = stream.receive().await.expect("failed to receive"); // just response with the same message. stream .send(client_message.into_message()) .await .expect("failed to send response") } } } #[tokio::test] async fn test_quic_stream() { let dns_name = "ns.example.com"; let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "../..".to_owned()); println!("using server src path: {server_path}"); let ca = tls_server::read_cert(Path::new(&format!("{server_path}/tests/test-data/ca.pem"))) .map_err(|e| format!("error reading cert: {e}")) .unwrap(); let cert = tls_server::read_cert(Path::new(&format!( "{server_path}/tests/test-data/cert.pem" ))) .map_err(|e| format!("error reading cert: {e}")) .unwrap(); let key = tls_server::read_key_from_pem(Path::new(&format!( "{server_path}/tests/test-data/cert.key" ))) .unwrap(); // All testing is only done on local addresses, construct the server let quic_ns = QuicServer::new(SocketAddr::from(([127, 0, 0, 1], 0)), cert, key) .await .expect("failed to initialize QuicServer"); // kick off the server let server_addr = quic_ns.local_addr().expect("no address"); println!("testing quic on: {server_addr}"); let server_join = tokio::spawn(server_responder(quic_ns)); // now construct the client let mut roots = rustls::RootCertStore::empty(); ca.iter() .try_for_each(|ca| roots.add(ca)) .expect("failed to build roots"); let mut client_config = ClientConfig::builder() .with_safe_defaults() .with_root_certificates(roots) .with_no_client_auth(); client_config.key_log = Arc::new(KeyLogFile::new()); let mut builder = QuicClientStreamBuilder::default(); builder.crypto_config(client_config); println!("starting quic connect"); let mut client_stream = builder .build(server_addr, dns_name.to_string()) .await .expect("failed to connect"); println!("connected client to server"); // create a test message, send and then receive... let mut message = Message::default(); message.add_query(Query::query( Name::from_str("www.example.test.").unwrap(), RecordType::AAAA, )); // TODO: we should make the finalizer easier to call so this round-trip serialization isn't necessary. let bytes = message.to_vec().unwrap(); let message = Message::from_vec(&bytes).unwrap(); let response = client_stream .send_message(message.clone().into()) .next() .await .expect("no response received") .expect("failed to read response"); assert_eq!(*response, message); // and finally kill the server server_join.abort(); } hickory-proto-0.24.0/src/rr/dns_class.rs000064400000000000000000000136731046102023000162630ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! class of DNS operations, in general always IN for internet #![allow(clippy::use_self)] use std::cmp::Ordering; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::error::*; use crate::serialize::binary::*; /// The DNS Record class #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] #[allow(dead_code)] pub enum DNSClass { /// Internet IN, /// Chaos CH, /// Hesiod HS, /// QCLASS NONE NONE, /// QCLASS * (ANY) ANY, /// Special class for OPT Version, it was overloaded for EDNS - RFC 6891 /// From the RFC: `Values lower than 512 MUST be treated as equal to 512` OPT(u16), /// Unknown DNSClass was parsed Unknown(u16), } impl FromStr for DNSClass { type Err = ProtoError; /// Convert from `&str` to `DNSClass` /// /// ``` /// use std::str::FromStr; /// use hickory_proto::rr::dns_class::DNSClass; /// /// let var: DNSClass = DNSClass::from_str("IN").unwrap(); /// assert_eq!(DNSClass::IN, var); /// ``` fn from_str(str: &str) -> ProtoResult { debug_assert!(str.chars().all(|x| !char::is_ascii_lowercase(&x))); match str { "IN" => Ok(Self::IN), "CH" => Ok(Self::CH), "HS" => Ok(Self::HS), "NONE" => Ok(Self::NONE), "ANY" | "*" => Ok(Self::ANY), _ => Err(ProtoErrorKind::UnknownDnsClassStr(str.to_string()).into()), } } } impl DNSClass { /// Convert from `u16` to `DNSClass` /// /// ``` /// use hickory_proto::rr::dns_class::DNSClass; /// /// let var = DNSClass::from_u16(1).unwrap(); /// assert_eq!(DNSClass::IN, var); /// ``` #[deprecated(note = "use u16::into instead, this is now infallible")] pub fn from_u16(value: u16) -> ProtoResult { match value { 1 => Ok(Self::IN), 3 => Ok(Self::CH), 4 => Ok(Self::HS), 254 => Ok(Self::NONE), 255 => Ok(Self::ANY), _ => Ok(Self::Unknown(value)), } } /// Return the OPT version from value pub fn for_opt(value: u16) -> Self { // From RFC 6891: `Values lower than 512 MUST be treated as equal to 512` let value = value.max(512); Self::OPT(value) } } impl BinEncodable for DNSClass { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.emit_u16((*self).into()) } } impl<'r> BinDecodable<'r> for DNSClass { fn read(decoder: &mut BinDecoder<'_>) -> ProtoResult { let this = Self::from( decoder.read_u16()?.unverified(/*DNSClass is verified as safe in processing this*/), ); Ok(this) } } // TODO make these a macro or annotation /// Convert from `DNSClass` to `&str` /// /// ``` /// use hickory_proto::rr::dns_class::DNSClass; /// /// let var: &'static str = DNSClass::IN.into(); /// assert_eq!("IN", var); /// ``` impl From for &'static str { fn from(rt: DNSClass) -> &'static str { match rt { DNSClass::IN => "IN", DNSClass::CH => "CH", DNSClass::HS => "HS", DNSClass::NONE => "NONE", DNSClass::ANY => "ANY", DNSClass::OPT(_) => "OPT", DNSClass::Unknown(_) => "UNKNOWN", } } } /// Convert from `u16` to `DNSClass` /// /// ``` /// use hickory_proto::rr::dns_class::DNSClass; /// /// let var: DNSClass = 1u16.into(); /// assert_eq!(DNSClass::IN, var); /// ``` impl From for DNSClass { fn from(value: u16) -> Self { match value { 1 => Self::IN, 3 => Self::CH, 4 => Self::HS, 254 => Self::NONE, 255 => Self::ANY, _ => Self::Unknown(value), } } } /// Convert from `DNSClass` to `u16` /// /// ``` /// use hickory_proto::rr::dns_class::DNSClass; /// /// let var: u16 = DNSClass::IN.into(); /// assert_eq!(1, var); /// ``` impl From for u16 { fn from(rt: DNSClass) -> Self { match rt { DNSClass::IN => 1, DNSClass::CH => 3, DNSClass::HS => 4, DNSClass::NONE => 254, DNSClass::ANY => 255, // see https://tools.ietf.org/html/rfc6891#section-6.1.2 DNSClass::OPT(max_payload_len) => max_payload_len.max(512), DNSClass::Unknown(unknown) => unknown, } } } impl PartialOrd for DNSClass { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for DNSClass { fn cmp(&self, other: &Self) -> Ordering { u16::from(*self).cmp(&u16::from(*other)) } } impl Display for DNSClass { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { f.write_str(Into::<&str>::into(*self)) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_order() { let ordered = vec![ DNSClass::IN, DNSClass::CH, DNSClass::HS, DNSClass::NONE, DNSClass::ANY, ]; let mut unordered = vec![ DNSClass::NONE, DNSClass::HS, DNSClass::CH, DNSClass::IN, DNSClass::ANY, ]; unordered.sort(); assert_eq!(unordered, ordered); } #[test] fn check_dns_class_parse_wont_panic_with_symbols() { let dns_class = "a-b-c".to_ascii_uppercase().parse::(); assert!(matches!(&dns_class, Err(ProtoError { .. }))); } } hickory-proto-0.24.0/src/rr/dnssec/algorithm.rs000064400000000000000000000254211046102023000175510ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. // needed for the derive statements on algorithm // this issue in rustc would help narrow the statement: https://github.com/rust-lang/rust/issues/62398 #![allow(deprecated, clippy::use_self)] use std::fmt; use std::fmt::{Display, Formatter}; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::error::*; use crate::serialize::binary::*; /// DNSSEC signing and validation algorithms. /// /// For [reference](https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml) /// the iana documents have all the officially registered algorithms. /// /// [RFC 6944](https://tools.ietf.org/html/rfc6944), DNSSEC DNSKEY Algorithm Status, April 2013 /// /// ```text /// /// 2.2. Algorithm Implementation Status Assignment Rationale /// /// RSASHA1 has an implementation status of Must Implement, consistent /// with [RFC4034]. RSAMD5 has an implementation status of Must Not /// Implement because of known weaknesses in MD5. /// /// The status of RSASHA1-NSEC3-SHA1 is set to Recommended to Implement /// as many deployments use NSEC3. The status of RSA/SHA-256 and RSA/ /// SHA-512 are also set to Recommended to Implement as major deployments /// (such as the root zone) use these algorithms [ROOTDPS]. It is /// believed that RSA/SHA-256 or RSA/SHA-512 algorithms will replace /// older algorithms (e.g., RSA/SHA-1) that have a perceived weakness. /// /// Likewise, ECDSA with the two identified curves (ECDSAP256SHA256 and /// ECDSAP384SHA384) is an algorithm that may see widespread use due to /// the perceived similar level of security offered with smaller key size /// compared to the key sizes of algorithms such as RSA. Therefore, /// ECDSAP256SHA256 and ECDSAP384SHA384 are Recommended to Implement. /// /// All other algorithms used in DNSSEC specified without an /// implementation status are currently set to Optional. /// /// 2.3. DNSSEC Implementation Status Table /// /// The DNSSEC algorithm implementation status table is listed below. /// Only the algorithms already specified for use with DNSSEC at the time /// of writing are listed. /// /// +------------+------------+-------------------+-------------------+ /// | Must | Must Not | Recommended | Optional | /// | Implement | Implement | to Implement | | /// +------------+------------+-------------------+-------------------+ /// | | | | | /// | RSASHA1 | RSAMD5 | RSASHA256 | Any | /// | | | RSASHA1-NSEC3 | registered | /// | | | -SHA1 | algorithm | /// | | | RSASHA512 | not listed in | /// | | | ECDSAP256SHA256 | this table | /// | | | ECDSAP384SHA384 | | /// +------------+------------+-------------------+-------------------+ /// /// This table does not list the Reserved values in the IANA registry /// table or the values for INDIRECT (252), PRIVATE (253), and PRIVATEOID /// (254). These values may relate to more than one algorithm and are /// therefore up to the implementer's discretion. As noted, any /// algorithm not listed in the table is Optional. As of this writing, /// the Optional algorithms are DSASHA1, DH, DSA-NSEC3-SHA1, and GOST- /// ECC, but in general, anything not explicitly listed is Optional. /// /// 2.4. Specifying New Algorithms and Updating the Status of Existing /// Entries /// /// [RFC6014] establishes a parallel procedure for adding a registry /// entry for a new algorithm other than a standards track document. /// Because any algorithm not listed in the foregoing table is Optional, /// algorithms entered into the registry using the [RFC6014] procedure /// are automatically Optional. /// /// It has turned out to be useful for implementations to refer to a /// single document that specifies the implementation status of every /// algorithm. Accordingly, when a new algorithm is to be registered /// with a status other than Optional, this document shall be made /// obsolete by a new document that adds the new algorithm to the table /// in Section 2.3. Similarly, if the status of any algorithm in the /// table in Section 2.3 changes, a new document shall make this document /// obsolete; that document shall include a replacement of the table in /// Section 2.3. This way, the goal of having one authoritative document /// to specify all the status values is achieved. /// /// This document cannot be updated, only made obsolete and replaced by a /// successor document. /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[non_exhaustive] pub enum Algorithm { /// DO NOT USE, MD5 is a compromised hashing function, it is here for backward compatibility #[deprecated( note = "this is a compromised hashing function, it is here for backward compatibility" )] RSAMD5, /// DO NOT USE, DSA is a compromised hashing function, it is here for backward compatibility #[deprecated( note = "this is a compromised hashing function, it is here for backward compatibility" )] DSA, /// DO NOT USE, SHA1 is a compromised hashing function, it is here for backward compatibility #[deprecated( note = "this is a compromised hashing function, it is here for backward compatibility" )] RSASHA1, /// DO NOT USE, SHA1 is a compromised hashing function, it is here for backward compatibility #[deprecated( note = "this is a compromised hashing function, it is here for backward compatibility" )] RSASHA1NSEC3SHA1, /// RSA public key with SHA256 hash RSASHA256, /// RSA public key with SHA512 hash RSASHA512, /// [rfc6605](https://tools.ietf.org/html/rfc6605) ECDSAP256SHA256, /// [rfc6605](https://tools.ietf.org/html/rfc6605) ECDSAP384SHA384, /// [draft-ietf-curdle-dnskey-eddsa-03](https://tools.ietf.org/html/draft-ietf-curdle-dnskey-eddsa-03) ED25519, /// An unknown algorithm identifier Unknown(u8), } impl Algorithm { /// pub fn from_u8(value: u8) -> Self { #[allow(deprecated)] match value { 1 => Self::RSAMD5, 3 => Self::DSA, 5 => Self::RSASHA1, 7 => Self::RSASHA1NSEC3SHA1, 8 => Self::RSASHA256, 10 => Self::RSASHA512, 13 => Self::ECDSAP256SHA256, 14 => Self::ECDSAP384SHA384, 15 => Self::ED25519, _ => Self::Unknown(value), } } /// length in bytes that the hash portion of this function will produce pub fn hash_len(self) -> Option { match self { Self::RSAMD5 => Some(16), // 128 bits Self::DSA | Self::RSASHA1 | Self::RSASHA1NSEC3SHA1 => Some(20), // 160 bits Self::RSASHA256 | Self::ECDSAP256SHA256 | Self::ED25519 => Some(32), // 256 bits Self::ECDSAP384SHA384 => Some(48), Self::RSASHA512 => Some(64), // 512 bites Self::Unknown(_) => None, } } /// Convert to string form #[deprecated(note = "use as_str instead")] pub fn to_str(self) -> &'static str { self.as_str() } /// Convert to string form pub fn as_str(self) -> &'static str { match self { Self::RSAMD5 => "RSAMD5", Self::DSA => "DSA", Self::RSASHA1 => "RSASHA1", Self::RSASHA256 => "RSASHA256", Self::RSASHA1NSEC3SHA1 => "RSASHA1-NSEC3-SHA1", Self::RSASHA512 => "RSASHA512", Self::ECDSAP256SHA256 => "ECDSAP256SHA256", Self::ECDSAP384SHA384 => "ECDSAP384SHA384", Self::ED25519 => "ED25519", Self::Unknown(_) => "Unknown", } } } impl BinEncodable for Algorithm { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.emit(u8::from(*self)) } } impl<'r> BinDecodable<'r> for Algorithm { // https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult { let algorithm_id = decoder.read_u8()?.unverified(/*Algorithm is verified as safe in processing this*/); Ok(Self::from_u8(algorithm_id)) } } impl From for &'static str { fn from(a: Algorithm) -> &'static str { a.as_str() } } impl From for u8 { fn from(a: Algorithm) -> Self { match a { Algorithm::RSAMD5 => 1, Algorithm::DSA => 3, Algorithm::RSASHA1 => 5, Algorithm::RSASHA1NSEC3SHA1 => 7, Algorithm::RSASHA256 => 8, Algorithm::RSASHA512 => 10, Algorithm::ECDSAP256SHA256 => 13, Algorithm::ECDSAP384SHA384 => 14, Algorithm::ED25519 => 15, Algorithm::Unknown(v) => v, } } } impl Display for Algorithm { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { f.write_str(self.as_str()) } } #[test] fn test_into() { for algorithm in &[ Algorithm::RSAMD5, Algorithm::DSA, Algorithm::RSASHA1, Algorithm::RSASHA256, Algorithm::RSASHA1NSEC3SHA1, Algorithm::RSASHA512, Algorithm::ECDSAP256SHA256, Algorithm::ECDSAP384SHA384, Algorithm::ED25519, ] { assert_eq!(*algorithm, Algorithm::from_u8(Into::::into(*algorithm))) } } #[test] fn test_order() { let mut algorithms = [ Algorithm::RSAMD5, Algorithm::DSA, Algorithm::RSASHA1, Algorithm::RSASHA256, Algorithm::RSASHA1NSEC3SHA1, Algorithm::RSASHA512, Algorithm::ECDSAP256SHA256, Algorithm::ECDSAP384SHA384, Algorithm::ED25519, ]; algorithms.sort(); for (got, expect) in algorithms.iter().zip( [ Algorithm::RSAMD5, Algorithm::DSA, Algorithm::RSASHA1, Algorithm::RSASHA1NSEC3SHA1, Algorithm::RSASHA256, Algorithm::RSASHA512, Algorithm::ECDSAP256SHA256, Algorithm::ECDSAP384SHA384, Algorithm::ED25519, ] .iter(), ) { assert_eq!(got, expect); } } hickory-proto-0.24.0/src/rr/dnssec/digest_type.rs000064400000000000000000000135171046102023000201060ustar 00000000000000// Copyright 2015-2022 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. #![allow(clippy::use_self)] #[cfg(feature = "openssl")] use openssl::hash; #[cfg(feature = "ring")] use ring::digest; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::error::*; use crate::rr::dnssec::Algorithm; #[cfg(any(feature = "ring", feature = "openssl"))] use super::Digest; /// This is the digest format for the /// ///```text /// 0 Reserved - [RFC3658] /// 1 SHA-1 MANDATORY [RFC3658] /// 2 SHA-256 MANDATORY [RFC4509] /// 3 GOST R 34.11-94 OPTIONAL [RFC5933] /// 4 SHA-384 OPTIONAL [RFC6605] /// 5 ED25519 [RFC draft-ietf-curdle-dnskey-eddsa-03] /// 5-255 Unassigned - /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[non_exhaustive] pub enum DigestType { /// [RFC 3658](https://tools.ietf.org/html/rfc3658) SHA1, /// [RFC 4509](https://tools.ietf.org/html/rfc4509) SHA256, /// [RFC 5933](https://tools.ietf.org/html/rfc5933) GOSTR34_11_94, /// [RFC 6605](https://tools.ietf.org/html/rfc6605) SHA384, /// Undefined SHA512, /// This is a passthrough digest as ED25519 is self-packaged ED25519, } impl DigestType { /// TODO: add an Unknown DigestType and make this infallible /// pub fn from_u8(value: u8) -> ProtoResult { match value { 1 => Ok(Self::SHA1), 2 => Ok(Self::SHA256), 3 => Ok(Self::GOSTR34_11_94), 4 => Ok(Self::SHA384), 5 => Ok(Self::ED25519), _ => Err(ProtoErrorKind::UnknownAlgorithmTypeValue(value).into()), } } /// The OpenSSL counterpart for the digest #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn to_openssl_digest(self) -> ProtoResult { match self { Self::SHA1 => Ok(hash::MessageDigest::sha1()), Self::SHA256 => Ok(hash::MessageDigest::sha256()), Self::SHA384 => Ok(hash::MessageDigest::sha384()), Self::SHA512 => Ok(hash::MessageDigest::sha512()), _ => Err(format!("digest not supported by openssl: {self:?}").into()), } } /// The *ring* counterpart for the digest #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn to_ring_digest_alg(self) -> ProtoResult<&'static digest::Algorithm> { match self { Self::SHA1 => Ok(&digest::SHA1_FOR_LEGACY_USE_ONLY), Self::SHA256 => Ok(&digest::SHA256), Self::SHA384 => Ok(&digest::SHA384), Self::SHA512 => Ok(&digest::SHA512), _ => Err(format!("digest not supported by ring: {self:?}").into()), } } /// Hash the data #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] pub fn hash(self, data: &[u8]) -> ProtoResult { hash::hash(self.to_openssl_digest()?, data).map_err(Into::into) } /// Hash the data #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn hash(self, data: &[u8]) -> ProtoResult { let alg = self.to_ring_digest_alg()?; Ok(digest::digest(alg, data)) } /// This will always error, enable openssl feature at compile time #[cfg(not(any(feature = "openssl", feature = "ring")))] #[cfg_attr(docsrs, doc(cfg(not(any(feature = "openssl", feature = "ring")))))] pub fn hash(self, _: &[u8]) -> ProtoResult> { Err("The openssl and ring features are both disabled".into()) } /// Digest all the data. #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] pub fn digest_all(self, data: &[&[u8]]) -> ProtoResult { use std::io::Write; let digest_type = self.to_openssl_digest()?; hash::Hasher::new(digest_type) .map_err(Into::into) .and_then(|mut hasher| { for d in data { hasher.write_all(d)?; } hasher.finish().map_err(Into::into) }) } /// Digest all the data. #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn digest_all(self, data: &[&[u8]]) -> ProtoResult { let alg = self.to_ring_digest_alg()?; let mut ctx = digest::Context::new(alg); for d in data { ctx.update(d); } Ok(ctx.finish()) } } impl From for DigestType { fn from(a: Algorithm) -> Self { #[allow(deprecated)] match a { Algorithm::RSAMD5 | Algorithm::DSA | Algorithm::RSASHA1 | Algorithm::RSASHA1NSEC3SHA1 => Self::SHA1, Algorithm::RSASHA256 | Algorithm::ECDSAP256SHA256 => Self::SHA256, Algorithm::RSASHA512 => Self::SHA512, Algorithm::ECDSAP384SHA384 => Self::SHA384, Algorithm::ED25519 => Self::ED25519, Algorithm::Unknown(_) => Self::SHA512, } } } impl From for u8 { fn from(a: DigestType) -> Self { match a { DigestType::SHA1 => 1, DigestType::SHA256 => 2, DigestType::GOSTR34_11_94 => 3, DigestType::SHA384 => 4, DigestType::ED25519 => 5, DigestType::SHA512 => 255, } } } hickory-proto-0.24.0/src/rr/dnssec/ec_public_key.rs000064400000000000000000000033031046102023000203530ustar 00000000000000// Copyright 2017 Brian Smith // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use super::Algorithm; use crate::error::*; #[allow(unreachable_pub)] #[derive(Clone, Copy, PartialEq, Eq)] pub struct ECPublicKey { buf: [u8; MAX_LEN], len: usize, } // The length of the longest supported EC public key (P-384). const MAX_LEN: usize = 1 + (2 * 48); #[allow(unreachable_pub)] impl ECPublicKey { // DNSSEC encodes uncompressed EC public keys without the standard 0x04 // prefix that indicates they are uncompressed, but crypto libraries // require that prefix. pub fn from_unprefixed(without_prefix: &[u8], algorithm: Algorithm) -> ProtoResult { let field_len = match algorithm { Algorithm::ECDSAP256SHA256 => 32, Algorithm::ECDSAP384SHA384 => 48, _ => return Err("only ECDSAP256SHA256 and ECDSAP384SHA384 are supported by Ec".into()), }; let len = 1 + (2 * field_len); if len - 1 != without_prefix.len() { return Err("EC public key is the wrong length".into()); } let mut buf = [0x04u8; MAX_LEN]; buf[1..len].copy_from_slice(without_prefix); Ok(Self { buf, len }) } pub fn prefixed_bytes(&self) -> &[u8] { &self.buf[..self.len] } #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn unprefixed_bytes(&self) -> &[u8] { &self.buf[1..self.len] } } hickory-proto-0.24.0/src/rr/dnssec/key_format.rs000064400000000000000000000333461046102023000177300ustar 00000000000000#[cfg(feature = "openssl")] use openssl::ec::EcKey; #[cfg(feature = "openssl")] use openssl::rsa::Rsa; #[cfg(feature = "openssl")] use openssl::symm::Cipher; #[cfg(feature = "ring")] use ring::signature::{ EcdsaKeyPair, Ed25519KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, }; use crate::error::*; use crate::rr::dnssec::{Algorithm, KeyPair, Private}; /// The format of the binary key #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum KeyFormat { /// A der encoded key Der, /// A pem encoded key, the default of OpenSSL Pem, /// Pkcs8, a pkcs8 formatted private key Pkcs8, } impl KeyFormat { /// Decode private key #[allow(unused, clippy::match_single_binding)] pub fn decode_key( self, bytes: &[u8], password: Option<&str>, algorithm: Algorithm, ) -> DnsSecResult> { // empty string prevents openssl from triggering a read from stdin... let password = password.unwrap_or(""); let password = password.as_bytes(); #[allow(deprecated)] match algorithm { Algorithm::Unknown(v) => Err(format!("unknown algorithm: {v}").into()), #[cfg(feature = "openssl")] e @ Algorithm::RSASHA1 | e @ Algorithm::RSASHA1NSEC3SHA1 => { Err(format!("unsupported Algorithm (insecure): {e:?}").into()) } #[cfg(feature = "openssl")] Algorithm::RSASHA256 | Algorithm::RSASHA512 => { let key = match self { Self::Der => Rsa::private_key_from_der(bytes) .map_err(|e| format!("error reading RSA as DER: {e}"))?, Self::Pem => { let key = Rsa::private_key_from_pem_passphrase(bytes, password); key.map_err(|e| { format!("could not decode RSA from PEM, bad password?: {e}") })? } e => { return Err(format!( "unsupported key format with RSA (DER or PEM only): \ {e:?}" ) .into()) } }; Ok(KeyPair::from_rsa(key) .map_err(|e| format!("could not translate RSA to KeyPair: {e}"))?) } Algorithm::ECDSAP256SHA256 | Algorithm::ECDSAP384SHA384 => match self { #[cfg(feature = "openssl")] Self::Der => { let key = EcKey::private_key_from_der(bytes) .map_err(|e| format!("error reading EC as DER: {e}"))?; Ok(KeyPair::from_ec_key(key) .map_err(|e| format!("could not translate RSA to KeyPair: {e}"))?) } #[cfg(feature = "openssl")] Self::Pem => { let key = EcKey::private_key_from_pem_passphrase(bytes, password) .map_err(|e| format!("could not decode EC from PEM, bad password?: {e}"))?; Ok(KeyPair::from_ec_key(key) .map_err(|e| format!("could not translate RSA to KeyPair: {e}"))?) } #[cfg(feature = "ring")] Self::Pkcs8 => { let ring_algorithm = if algorithm == Algorithm::ECDSAP256SHA256 { &ECDSA_P256_SHA256_FIXED_SIGNING } else { &ECDSA_P384_SHA384_FIXED_SIGNING }; let key = EcdsaKeyPair::from_pkcs8(ring_algorithm, bytes)?; Ok(KeyPair::from_ecdsa(key)) } e => Err(format!("unsupported key format with EC: {e:?}").into()), }, Algorithm::ED25519 => match self { #[cfg(feature = "ring")] Self::Pkcs8 => { let key = Ed25519KeyPair::from_pkcs8(bytes)?; Ok(KeyPair::from_ed25519(key)) } e => Err(format!( "unsupported key format with ED25519 (only Pkcs8 supported): {e:?}" ) .into()), }, e => { Err(format!("unsupported Algorithm, enable openssl or ring feature: {e:?}").into()) } } } /// Generate a new key and encode to the specified format pub fn generate_and_encode( self, algorithm: Algorithm, password: Option<&str>, ) -> DnsSecResult> { // on encoding, if the password is empty string, ignore it (empty string is ok on decode) #[allow(unused)] let password = password .iter() .filter(|s| !s.is_empty()) .map(|s| s.as_bytes()) .next(); // generate the key #[allow(unused, deprecated)] let key_pair: KeyPair = match algorithm { Algorithm::Unknown(v) => return Err(format!("unknown algorithm: {v}").into()), #[cfg(feature = "openssl")] e @ Algorithm::RSASHA1 | e @ Algorithm::RSASHA1NSEC3SHA1 => { return Err(format!("unsupported Algorithm (insecure): {e:?}").into()) } #[cfg(feature = "openssl")] Algorithm::RSASHA256 | Algorithm::RSASHA512 => KeyPair::generate(algorithm)?, Algorithm::ECDSAP256SHA256 | Algorithm::ECDSAP384SHA384 => match self { #[cfg(feature = "openssl")] Self::Der | Self::Pem => KeyPair::generate(algorithm)?, #[cfg(feature = "ring")] Self::Pkcs8 => return KeyPair::generate_pkcs8(algorithm), e => return Err(format!("unsupported key format with EC: {e:?}").into()), }, #[cfg(feature = "ring")] Algorithm::ED25519 => return KeyPair::generate_pkcs8(algorithm), e => { return Err( format!("unsupported Algorithm, enable openssl or ring feature: {e:?}").into(), ) } }; // encode the key #[allow(unreachable_code)] match key_pair { #[cfg(feature = "openssl")] KeyPair::EC(ref pkey) | KeyPair::RSA(ref pkey) => { match self { Self::Der => { // to avoid accidentally storing a key where there was an expectation that it was password protected if password.is_some() { return Err(format!("Can only password protect PEM: {self:?}").into()); } pkey.private_key_to_der() .map_err(|e| format!("error writing key as DER: {e}").into()) } Self::Pem => { let key = if let Some(password) = password { pkey.private_key_to_pem_pkcs8_passphrase( Cipher::aes_256_cbc(), password, ) } else { pkey.private_key_to_pem_pkcs8() }; key.map_err(|e| format!("error writing key as PEM: {e}").into()) } e => Err(format!( "unsupported key format with RSA or EC (DER or PEM \ only): {e:?}" ) .into()), } } #[cfg(feature = "ring")] KeyPair::ECDSA(..) | KeyPair::ED25519(..) => panic!("should have returned early"), #[cfg(not(feature = "openssl"))] KeyPair::Phantom(..) => panic!("Phantom disallowed"), #[cfg(not(any(feature = "openssl", feature = "ring")))] _ => Err(format!( "unsupported Algorithm, enable openssl feature (encode not supported with ring)" ) .into()), } } /// Encode private key #[deprecated] pub fn encode_key( self, key_pair: &KeyPair, password: Option<&str>, ) -> DnsSecResult> { // on encoding, if the password is empty string, ignore it (empty string is ok on decode) #[allow(unused)] let password = password .iter() .filter(|s| !s.is_empty()) .map(|s| s.as_bytes()) .next(); match *key_pair { #[cfg(feature = "openssl")] KeyPair::EC(ref pkey) | KeyPair::RSA(ref pkey) => { match self { Self::Der => { // to avoid accidentally storing a key where there was an expectation that it was password protected if password.is_some() { return Err(format!("Can only password protect PEM: {self:?}").into()); } pkey.private_key_to_der() .map_err(|e| format!("error writing key as DER: {e}").into()) } Self::Pem => { let key = if let Some(password) = password { pkey.private_key_to_pem_pkcs8_passphrase( Cipher::aes_256_cbc(), password, ) } else { pkey.private_key_to_pem_pkcs8() }; key.map_err(|e| format!("error writing key as PEM: {e}").into()) } e => Err(format!( "unsupported key format with RSA or EC (DER or PEM \ only): {e:?}" ) .into()), } } #[cfg(any(feature = "ring", not(feature = "openssl")))] _ => Err( "unsupported Algorithm, enable openssl feature (encode not supported with ring)" .into(), ), } } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; #[test] #[cfg(feature = "openssl")] fn test_rsa_encode_decode_der() { let algorithm = Algorithm::RSASHA256; encode_decode_with_format(KeyFormat::Der, algorithm, false, true); } #[test] #[cfg(feature = "openssl")] fn test_rsa_encode_decode_pem() { let algorithm = Algorithm::RSASHA256; encode_decode_with_format(KeyFormat::Pem, algorithm, true, true); } #[test] #[cfg(feature = "openssl")] fn test_ec_encode_decode_der() { let algorithm = Algorithm::ECDSAP256SHA256; encode_decode_with_format(KeyFormat::Der, algorithm, false, true); } #[test] #[cfg(feature = "openssl")] fn test_ec_encode_decode_pem() { let algorithm = Algorithm::ECDSAP256SHA256; encode_decode_with_format(KeyFormat::Pem, algorithm, true, true); } #[test] #[cfg(feature = "ring")] fn test_ec_encode_decode_pkcs8() { let algorithm = Algorithm::ECDSAP256SHA256; encode_decode_with_format(KeyFormat::Pkcs8, algorithm, true, true); } #[test] #[cfg(feature = "ring")] fn test_ed25519_encode_decode_pkcs8() { let algorithm = Algorithm::ED25519; encode_decode_with_format(KeyFormat::Pkcs8, algorithm, true, true); } #[cfg(test)] fn encode_decode_with_format( key_format: KeyFormat, algorithm: Algorithm, ok_pass: bool, ok_empty_pass: bool, ) { let password = Some("test password"); let empty_password = Some(""); let no_password = None::<&str>; encode_decode_with_password(key_format, password, password, algorithm, ok_pass, true); encode_decode_with_password( key_format, empty_password, empty_password, algorithm, ok_empty_pass, true, ); encode_decode_with_password( key_format, no_password, no_password, algorithm, ok_empty_pass, true, ); encode_decode_with_password( key_format, no_password, empty_password, algorithm, ok_empty_pass, true, ); encode_decode_with_password( key_format, empty_password, no_password, algorithm, ok_empty_pass, true, ); // TODO: disabled for now... add back in if ring supports passwords on pkcs8 // encode_decode_with_password(key_format, // password, // no_password, // algorithm, // ok_pass, // false); } #[cfg(test)] fn encode_decode_with_password( key_format: KeyFormat, en_pass: Option<&str>, de_pass: Option<&str>, algorithm: Algorithm, encode: bool, decode: bool, ) { println!( "test params: format: {key_format:?}, en_pass: {en_pass:?}, de_pass: {de_pass:?}, alg: {algorithm:?}, encode: {encode}, decode: {decode}" ); let encoded_rslt = key_format.generate_and_encode(algorithm, en_pass); if encode { let encoded = encoded_rslt.expect("Encoding error"); let decoded = key_format.decode_key(&encoded, de_pass, algorithm); assert_eq!(decoded.is_ok(), decode); } else { assert!(encoded_rslt.is_err()); } } } hickory-proto-0.24.0/src/rr/dnssec/keypair.rs000064400000000000000000000600021046102023000172210ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. #[cfg(not(feature = "openssl"))] use std::marker::PhantomData; #[cfg(feature = "openssl")] use openssl::bn::BigNumContext; #[cfg(feature = "openssl")] use openssl::ec::{EcGroup, EcKey, PointConversionForm}; #[cfg(feature = "openssl")] use openssl::nid::Nid; #[cfg(feature = "openssl")] use openssl::pkey::PKey; #[cfg(feature = "openssl")] use openssl::rsa::Rsa as OpenSslRsa; #[cfg(feature = "openssl")] use openssl::sign::Signer; #[allow(deprecated)] use crate::rr::dnssec::rdata::key::{KeyTrust, Protocol, UpdateScope}; #[cfg(feature = "ring")] use ring::{ rand, signature::{ EcdsaKeyPair, Ed25519KeyPair, KeyPair as RingKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, }, }; use crate::error::*; use crate::rr::dnssec::rdata::key::KeyUsage; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::rr::dnssec::rdata::DS; use crate::rr::dnssec::rdata::{DNSKEY, KEY}; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::rr::dnssec::DigestType; use crate::rr::dnssec::{Algorithm, PublicKeyBuf}; use crate::rr::dnssec::{HasPrivate, HasPublic, Private, TBS}; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::rr::Name; /// A public and private key pair, the private portion is not required. /// /// This supports all the various public/private keys which Hickory DNS is capable of using. Given /// differing features, some key types may not be available. The `openssl` feature will enable RSA and EC /// (P256 and P384). The `ring` feature enables ED25519, in the future, Ring will also be used for other keys. #[allow(clippy::large_enum_variant)] pub enum KeyPair { /// RSA keypair, supported by OpenSSL #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] RSA(PKey), /// Elliptic curve keypair, supported by OpenSSL #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] EC(PKey), #[cfg(not(feature = "openssl"))] #[doc(hidden)] Phantom(PhantomData), /// *ring* ECDSA keypair #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] ECDSA(EcdsaKeyPair), /// ED25519 encryption and hash defined keypair #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] ED25519(Ed25519KeyPair), } impl KeyPair { /// Creates an RSA type keypair. #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn from_rsa(rsa: OpenSslRsa) -> DnsSecResult { PKey::from_rsa(rsa).map(Self::RSA).map_err(Into::into) } /// Given a known pkey of an RSA key, return the wrapped keypair #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn from_rsa_pkey(pkey: PKey) -> Self { Self::RSA(pkey) } /// Creates an EC, elliptic curve, type keypair, only P256 or P384 are supported. #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn from_ec_key(ec_key: EcKey) -> DnsSecResult { PKey::from_ec_key(ec_key).map(Self::EC).map_err(Into::into) } /// Given a known pkey of an EC key, return the wrapped keypair #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn from_ec_pkey(pkey: PKey) -> Self { Self::EC(pkey) } /// Creates an ECDSA keypair with ring. #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn from_ecdsa(ec_key: EcdsaKeyPair) -> Self { Self::ECDSA(ec_key) } /// Creates an ED25519 keypair. #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn from_ed25519(ed_key: Ed25519KeyPair) -> Self { Self::ED25519(ed_key) } } impl KeyPair { /// Converts this keypair to the DNS binary form of the public_key. /// /// If there is a private key associated with this keypair, it will not be included in this /// format. Only the public key material will be included. pub fn to_public_bytes(&self) -> DnsSecResult> { #[allow(unreachable_patterns)] match *self { // see from_vec() RSA sections for reference #[cfg(feature = "openssl")] Self::RSA(ref pkey) => { let mut bytes: Vec = Vec::new(); // TODO: make these expects a try! and Err() let rsa: OpenSslRsa = pkey .rsa() .expect("pkey should have been initialized with RSA"); // this is to get us access to the exponent and the modulus let e: Vec = rsa.e().to_vec(); let n: Vec = rsa.n().to_vec(); if e.len() > 255 { bytes.push(0); bytes.push((e.len() >> 8) as u8); } bytes.push(e.len() as u8); bytes.extend_from_slice(&e); bytes.extend_from_slice(&n); Ok(bytes) } // see from_vec() ECDSA sections for reference #[cfg(feature = "openssl")] Self::EC(ref pkey) => { // TODO: make these expects a try! and Err() let ec_key: EcKey = pkey .ec_key() .expect("pkey should have been initialized with EC"); let group = ec_key.group(); let point = ec_key.public_key(); let mut bytes = BigNumContext::new() .and_then(|mut ctx| { point.to_bytes(group, PointConversionForm::UNCOMPRESSED, &mut ctx) }) .map_err(DnsSecError::from)?; // Remove OpenSSL header byte bytes.remove(0); Ok(bytes) } #[cfg(feature = "ring")] Self::ECDSA(ref ec_key) => { let mut bytes: Vec = ec_key.public_key().as_ref().to_vec(); bytes.remove(0); Ok(bytes) } #[cfg(feature = "ring")] Self::ED25519(ref ed_key) => Ok(ed_key.public_key().as_ref().to_vec()), #[cfg(not(feature = "openssl"))] Self::Phantom(..) => panic!("Phantom disallowed"), #[cfg(not(any(feature = "openssl", feature = "ring")))] _ => Err(DnsSecErrorKind::Message("openssl or ring feature(s) not enabled").into()), } } /// Returns a PublicKeyBuf of the KeyPair pub fn to_public_key(&self) -> DnsSecResult { Ok(PublicKeyBuf::new(self.to_public_bytes()?)) } /// The key tag is calculated as a hash to more quickly lookup a DNSKEY. /// /// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987 /// /// ```text /// RFC 2535 DNS Security Extensions March 1999 /// /// 4.1.6 Key Tag Field /// /// The "key Tag" is a two octet quantity that is used to efficiently /// select between multiple keys which may be applicable and thus check /// that a public key about to be used for the computationally expensive /// effort to check the signature is possibly valid. For algorithm 1 /// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two /// octets of the public key modulus needed to decode the signature /// field. That is to say, the most significant 16 of the least /// significant 24 bits of the modulus in network (big endian) order. For /// all other algorithms, including private algorithms, it is calculated /// as a simple checksum of the KEY RR as described in Appendix C. /// /// Appendix C: Key Tag Calculation /// /// The key tag field in the SIG RR is just a means of more efficiently /// selecting the correct KEY RR to use when there is more than one KEY /// RR candidate available, for example, in verifying a signature. It is /// possible for more than one candidate key to have the same tag, in /// which case each must be tried until one works or all fail. The /// following reference implementation of how to calculate the Key Tag, /// for all algorithms other than algorithm 1, is in ANSI C. It is coded /// for clarity, not efficiency. (See section 4.1.6 for how to determine /// the Key Tag of an algorithm 1 key.) /// /// /* assumes int is at least 16 bits /// first byte of the key tag is the most significant byte of return /// value /// second byte of the key tag is the least significant byte of /// return value /// */ /// /// int keytag ( /// /// unsigned char key[], /* the RDATA part of the KEY RR */ /// unsigned int keysize, /* the RDLENGTH */ /// ) /// { /// long int ac; /* assumed to be 32 bits or larger */ /// /// for ( ac = 0, i = 0; i < keysize; ++i ) /// ac += (i&1) ? key[i] : key[i]<<8; /// ac += (ac>>16) & 0xFFFF; /// return ac & 0xFFFF; /// } /// ``` pub fn key_tag(&self) -> DnsSecResult { let mut ac: usize = 0; for (i, k) in self.to_public_bytes()?.iter().enumerate() { ac += if i & 0x0001 == 0x0001 { *k as usize } else { (*k as usize) << 8 }; } ac += (ac >> 16) & 0xFFFF; Ok((ac & 0xFFFF) as u16) // this is unnecessary, no? } /// Creates a Record that represents the public key for this Signer /// /// # Arguments /// /// * `algorithm` - algorithm of the DNSKEY /// /// # Return /// /// the DNSKEY record data pub fn to_dnskey(&self, algorithm: Algorithm) -> DnsSecResult { self.to_public_bytes() .map(|bytes| DNSKEY::new(true, true, false, algorithm, bytes)) } /// Convert this keypair into a KEY record type for usage with SIG0 /// with key type entity (`KeyUsage::Entity`). /// /// # Arguments /// /// * `algorithm` - algorithm of the KEY /// /// # Return /// /// the KEY record data pub fn to_sig0key(&self, algorithm: Algorithm) -> DnsSecResult { self.to_sig0key_with_usage(algorithm, KeyUsage::default()) } /// Convert this keypair into a KEY record type for usage with SIG0 /// with a given key (usage) type. /// /// # Arguments /// /// * `algorithm` - algorithm of the KEY /// * `usage` - the key type /// /// # Return /// /// the KEY record data pub fn to_sig0key_with_usage( &self, algorithm: Algorithm, usage: KeyUsage, ) -> DnsSecResult { self.to_public_bytes().map(|bytes| { KEY::new( KeyTrust::default(), usage, #[allow(deprecated)] UpdateScope::default(), Protocol::default(), algorithm, bytes, ) }) } /// Creates a DS record for this KeyPair associated to the given name /// /// # Arguments /// /// * `name` - name of the DNSKEY record covered by the new DS record /// * `algorithm` - the algorithm of the DNSKEY /// * `digest_type` - the digest_type used to #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] pub fn to_ds( &self, name: &Name, algorithm: Algorithm, digest_type: DigestType, ) -> DnsSecResult { self.to_dnskey(algorithm) .and_then(|dnskey| self.key_tag().map(|key_tag| (key_tag, dnskey))) .and_then(|(key_tag, dnskey)| { dnskey .to_digest(name, digest_type) .map(|digest| (key_tag, digest)) .map_err(Into::into) }) .map(|(key_tag, digest)| { DS::new(key_tag, algorithm, digest_type, digest.as_ref().to_owned()) }) } } impl KeyPair { /// Signs a hash. /// /// This will panic if the `key` is not a private key and can be used for signing. /// /// # Arguments /// /// * `message` - the message bytes to be signed, see `rrset_tbs`. /// /// # Return value /// /// The signature, ready to be stored in an `RData::RRSIG`. #[allow(unused)] pub fn sign(&self, algorithm: Algorithm, tbs: &TBS) -> DnsSecResult> { use std::iter; match *self { #[cfg(feature = "openssl")] Self::RSA(ref pkey) | Self::EC(ref pkey) => { let digest_type = DigestType::from(algorithm).to_openssl_digest()?; let mut signer = Signer::new(digest_type, pkey)?; signer.update(tbs.as_ref())?; signer.sign_to_vec().map_err(Into::into).and_then(|bytes| { if let Self::RSA(_) = *self { return Ok(bytes); } // Convert DER signature to raw signature (see RFC 6605 Section 4) if bytes.len() < 8 { return Err("unexpected signature format (length too short)".into()); } let expect = |pos: usize, expected: u8| -> DnsSecResult<()> { if bytes[pos] != expected { return Err(format!( "unexpected signature format ({pos}, {expected}))" ) .into()); } Ok(()) }; // Sanity checks expect(0, 0x30)?; expect(1, (bytes.len() - 2) as u8)?; expect(2, 0x02)?; let p1_len = bytes[3] as usize; let p2_pos = 4 + p1_len; expect(p2_pos, 0x02)?; let p2_len = bytes[p2_pos + 1] as usize; if p2_pos + 2 + p2_len > bytes.len() { return Err("unexpected signature format (invalid length)".into()); } let p1 = &bytes[4..p2_pos]; let p2 = &bytes[p2_pos + 2..p2_pos + 2 + p2_len]; // For P-256, each integer MUST be encoded as 32 octets; // for P-384, each integer MUST be encoded as 48 octets. let part_len = match algorithm { Algorithm::ECDSAP256SHA256 => 32, Algorithm::ECDSAP384SHA384 => 48, _ => return Err("unexpected algorithm".into()), }; let mut ret = Vec::::new(); { let mut write_part = |mut part: &[u8]| -> DnsSecResult<()> { // We need to pad or trim the octet string to expected length if part.len() > part_len + 1 { return Err("invalid signature data".into()); } if part.len() == part_len + 1 { // Trim leading zero if part[0] != 0x00 { return Err("invalid signature data".into()); } part = &part[1..]; } // Pad with zeros. All numbers are big-endian here. ret.extend(iter::repeat(0x00).take(part_len - part.len())); ret.extend(part); Ok(()) }; write_part(p1)?; write_part(p2)?; } assert_eq!(ret.len(), part_len * 2); Ok(ret) }) } #[cfg(feature = "ring")] Self::ECDSA(ref ec_key) => { let rng = rand::SystemRandom::new(); Ok(ec_key.sign(&rng, tbs.as_ref())?.as_ref().to_vec()) } #[cfg(feature = "ring")] Self::ED25519(ref ed_key) => Ok(ed_key.sign(tbs.as_ref()).as_ref().to_vec()), #[cfg(not(feature = "openssl"))] Self::Phantom(..) => panic!("Phantom disallowed"), #[cfg(not(any(feature = "openssl", feature = "ring")))] _ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()), } } } impl KeyPair { /// Generates a new private and public key pair for the specified algorithm. /// /// RSA keys are hardcoded to 2048bits at the moment. Other keys have predefined sizes. pub fn generate(algorithm: Algorithm) -> DnsSecResult { #[allow(deprecated)] match algorithm { Algorithm::Unknown(_) => Err(DnsSecErrorKind::Message("unknown algorithm").into()), #[cfg(feature = "openssl")] Algorithm::RSASHA1 | Algorithm::RSASHA1NSEC3SHA1 | Algorithm::RSASHA256 | Algorithm::RSASHA512 => { // TODO: the only keysize right now, would be better for people to use other algorithms... OpenSslRsa::generate(2048) .map_err(Into::into) .and_then(Self::from_rsa) } #[cfg(feature = "openssl")] Algorithm::ECDSAP256SHA256 => EcGroup::from_curve_name(Nid::X9_62_PRIME256V1) .and_then(|group| EcKey::generate(&group)) .map_err(Into::into) .and_then(Self::from_ec_key), #[cfg(feature = "openssl")] Algorithm::ECDSAP384SHA384 => EcGroup::from_curve_name(Nid::SECP384R1) .and_then(|group| EcKey::generate(&group)) .map_err(Into::into) .and_then(Self::from_ec_key), #[cfg(feature = "ring")] Algorithm::ED25519 => Err(DnsSecErrorKind::Message( "use generate_pkcs8 for generating private key and encoding", ) .into()), _ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()), } } /// Generates a key, securing it with pkcs8 #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn generate_pkcs8(algorithm: Algorithm) -> DnsSecResult> { #[allow(deprecated)] match algorithm { Algorithm::Unknown(_) => Err(DnsSecErrorKind::Message("unknown algorithm").into()), #[cfg(feature = "openssl")] Algorithm::RSASHA1 | Algorithm::RSASHA1NSEC3SHA1 | Algorithm::RSASHA256 | Algorithm::RSASHA512 => { Err(DnsSecErrorKind::Message("openssl does not yet support pkcs8").into()) } #[cfg(feature = "ring")] Algorithm::ECDSAP256SHA256 => { let rng = rand::SystemRandom::new(); EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng) .map_err(Into::into) .map(|pkcs8_bytes| pkcs8_bytes.as_ref().to_vec()) } #[cfg(feature = "ring")] Algorithm::ECDSAP384SHA384 => { let rng = rand::SystemRandom::new(); EcdsaKeyPair::generate_pkcs8(&ECDSA_P384_SHA384_FIXED_SIGNING, &rng) .map_err(Into::into) .map(|pkcs8_bytes| pkcs8_bytes.as_ref().to_vec()) } #[cfg(feature = "ring")] Algorithm::ED25519 => { let rng = rand::SystemRandom::new(); Ed25519KeyPair::generate_pkcs8(&rng) .map_err(Into::into) .map(|pkcs8_bytes| pkcs8_bytes.as_ref().to_vec()) } _ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()), } } } #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg(test)] mod tests { use crate::rr::dnssec::TBS; use crate::rr::dnssec::*; #[cfg(feature = "openssl")] #[test] fn test_rsa() { public_key_test(Algorithm::RSASHA256, KeyFormat::Der); hash_test(Algorithm::RSASHA256, KeyFormat::Der); } #[cfg(feature = "openssl")] #[test] fn test_ec_p256() { public_key_test(Algorithm::ECDSAP256SHA256, KeyFormat::Der); hash_test(Algorithm::ECDSAP256SHA256, KeyFormat::Der); } #[cfg(feature = "ring")] #[test] fn test_ec_p256_pkcs8() { public_key_test(Algorithm::ECDSAP256SHA256, KeyFormat::Pkcs8); hash_test(Algorithm::ECDSAP256SHA256, KeyFormat::Pkcs8); } #[cfg(feature = "openssl")] #[test] fn test_ec_p384() { public_key_test(Algorithm::ECDSAP384SHA384, KeyFormat::Der); hash_test(Algorithm::ECDSAP384SHA384, KeyFormat::Der); } #[cfg(feature = "ring")] #[test] fn test_ec_p384_pkcs8() { public_key_test(Algorithm::ECDSAP384SHA384, KeyFormat::Pkcs8); hash_test(Algorithm::ECDSAP384SHA384, KeyFormat::Pkcs8); } #[cfg(feature = "ring")] #[test] fn test_ed25519() { public_key_test(Algorithm::ED25519, KeyFormat::Pkcs8); hash_test(Algorithm::ED25519, KeyFormat::Pkcs8); } #[allow(clippy::uninlined_format_args)] fn public_key_test(algorithm: Algorithm, key_format: KeyFormat) { let key = key_format .decode_key( &key_format.generate_and_encode(algorithm, None).unwrap(), None, algorithm, ) .unwrap(); let pk = key.to_public_key().unwrap(); let tbs = TBS::from(&b"www.example.com"[..]); let mut sig = key.sign(algorithm, &tbs).unwrap(); assert!( pk.verify(algorithm, tbs.as_ref(), &sig).is_ok(), "algorithm: {:?} (public key)", algorithm ); sig[10] = !sig[10]; assert!( pk.verify(algorithm, tbs.as_ref(), &sig).is_err(), "algorithm: {:?} (public key, neg)", algorithm ); } #[allow(clippy::uninlined_format_args)] fn hash_test(algorithm: Algorithm, key_format: KeyFormat) { let tbs = TBS::from(&b"www.example.com"[..]); // TODO: convert to stored keys... let key = key_format .decode_key( &key_format.generate_and_encode(algorithm, None).unwrap(), None, algorithm, ) .unwrap(); let pub_key = key.to_public_key().unwrap(); let neg = key_format .decode_key( &key_format.generate_and_encode(algorithm, None).unwrap(), None, algorithm, ) .unwrap(); let neg_pub_key = neg.to_public_key().unwrap(); let sig = key.sign(algorithm, &tbs).unwrap(); assert!( pub_key.verify(algorithm, tbs.as_ref(), &sig).is_ok(), "algorithm: {:?}", algorithm ); assert!( key.to_dnskey(algorithm) .unwrap() .verify(tbs.as_ref(), &sig) .is_ok(), "algorithm: {:?} (dnskey)", algorithm ); assert!( neg_pub_key.verify(algorithm, tbs.as_ref(), &sig).is_err(), "algorithm: {:?} (neg)", algorithm ); assert!( neg.to_dnskey(algorithm) .unwrap() .verify(tbs.as_ref(), &sig) .is_err(), "algorithm: {:?} (dnskey, neg)", algorithm ); } } hickory-proto-0.24.0/src/rr/dnssec/mod.rs000064400000000000000000000070661046102023000163470ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! dns security extension related modules mod algorithm; mod digest_type; #[cfg(any(feature = "openssl", feature = "ring"))] mod ec_public_key; mod key_format; mod keypair; mod nsec3; pub mod public_key; pub mod rdata; #[cfg(any(feature = "openssl", feature = "ring"))] mod rsa_public_key; mod signer; mod supported_algorithm; pub mod tbs; mod trust_anchor; pub mod tsig; mod verifier; pub use self::algorithm::Algorithm; pub use self::digest_type::DigestType; pub use self::nsec3::Nsec3HashAlgorithm; pub use self::public_key::PublicKey; pub use self::public_key::PublicKeyBuf; pub use self::public_key::PublicKeyEnum; pub use self::supported_algorithm::SupportedAlgorithms; pub use self::tbs::TBS; pub use self::trust_anchor::TrustAnchor; pub use self::verifier::Verifier; pub use crate::error::DnsSecResult; #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] pub use openssl::hash::DigestBytes as Digest; #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub use ring::digest::Digest; /// This is an empty type, enable Ring or OpenSSL for this feature #[cfg(not(any(feature = "openssl", feature = "ring")))] #[cfg_attr(docsrs, doc(cfg(not(any(feature = "openssl", feature = "ring")))))] #[derive(Clone, Copy, Debug)] pub struct Digest; #[cfg(not(any(feature = "openssl", feature = "ring")))] #[cfg_attr(docsrs, doc(cfg(not(any(feature = "openssl", feature = "ring")))))] #[allow(clippy::should_implement_trait)] impl Digest { /// This is an empty type, enable Ring or OpenSSL for this feature pub fn as_ref(&self) -> &Self { self } /// This is an empty type, enable Ring or OpenSSL for this feature #[allow(clippy::wrong_self_convention)] pub fn to_owned(&self) -> Vec { vec![] } } #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] pub use self::key_format::KeyFormat; pub use self::keypair::KeyPair; #[allow(deprecated)] pub use self::signer::{SigSigner, Signer}; #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub use openssl::pkey::{HasPrivate, HasPublic, Private, Public}; #[cfg(not(feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "openssl"))))] pub use self::faux_key_type::{HasPrivate, HasPublic, Private, Public}; #[cfg(not(feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "openssl"))))] mod faux_key_type { /// A key that contains public key material pub trait HasPublic {} /// A key that contains private key material pub trait HasPrivate {} impl HasPublic for K {} /// Faux implementation of the Openssl Public key types #[derive(Clone, Copy)] pub enum Public {} impl HasPublic for Public {} /// Faux implementation of the Openssl Public key types #[derive(Clone, Copy)] pub enum Private {} impl HasPrivate for Private {} } hickory-proto-0.24.0/src/rr/dnssec/nsec3.rs000064400000000000000000000241121046102023000165720ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * Copyright (C) 2017 Google LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! NSEC3 related record types #![allow(clippy::use_self)] #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; #[cfg(any(feature = "openssl", feature = "ring"))] use super::{Digest, DigestType}; use crate::error::*; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::rr::Name; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::serialize::binary::{BinEncodable, BinEncoder}; /// ```text /// RFC 5155 NSEC3 March 2008 /// /// 11. IANA Considerations /// /// Although the NSEC3 and NSEC3PARAM RR formats include a hash algorithm /// parameter, this document does not define a particular mechanism for /// safely transitioning from one NSEC3 hash algorithm to another. When /// specifying a new hash algorithm for use with NSEC3, a transition /// mechanism MUST also be defined. /// /// This document updates the IANA registry "DOMAIN NAME SYSTEM /// PARAMETERS" (https://www.iana.org/assignments/dns-parameters) in sub- /// registry "TYPES", by defining two new types. Section 3 defines the /// NSEC3 RR type 50. Section 4 defines the NSEC3PARAM RR type 51. /// /// This document updates the IANA registry "DNS SECURITY ALGORITHM /// NUMBERS -- per [RFC4035]" /// (https://www.iana.org/assignments/dns-sec-alg-numbers). Section 2 /// defines the aliases DSA-NSEC3-SHA1 (6) and RSASHA1-NSEC3-SHA1 (7) for /// respectively existing registrations DSA and RSASHA1 in combination /// with NSEC3 hash algorithm SHA1. /// /// Since these algorithm numbers are aliases for existing DNSKEY /// algorithm numbers, the flags that exist for the original algorithm /// are valid for the alias algorithm. /// /// This document creates a new IANA registry for NSEC3 flags. This /// registry is named "DNSSEC NSEC3 Flags". The initial contents of this /// registry are: /// /// 0 1 2 3 4 5 6 7 /// +---+---+---+---+---+---+---+---+ /// | | | | | | | |Opt| /// | | | | | | | |Out| /// +---+---+---+---+---+---+---+---+ /// /// bit 7 is the Opt-Out flag. /// /// bits 0 - 6 are available for assignment. /// /// Assignment of additional NSEC3 Flags in this registry requires IETF /// Standards Action [RFC2434]. /// /// This document creates a new IANA registry for NSEC3PARAM flags. This /// registry is named "DNSSEC NSEC3PARAM Flags". The initial contents of /// this registry are: /// /// 0 1 2 3 4 5 6 7 /// +---+---+---+---+---+---+---+---+ /// | | | | | | | | 0 | /// +---+---+---+---+---+---+---+---+ /// /// bit 7 is reserved and must be 0. /// /// bits 0 - 6 are available for assignment. /// /// Assignment of additional NSEC3PARAM Flags in this registry requires /// IETF Standards Action [RFC2434]. /// /// Finally, this document creates a new IANA registry for NSEC3 hash /// algorithms. This registry is named "DNSSEC NSEC3 Hash Algorithms". /// The initial contents of this registry are: /// /// 0 is Reserved. /// /// 1 is SHA-1. /// /// 2-255 Available for assignment. /// /// Assignment of additional NSEC3 hash algorithms in this registry /// requires IETF Standards Action [RFC2434]. /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum Nsec3HashAlgorithm { /// Hash for the Nsec3 records SHA1, } impl Nsec3HashAlgorithm { /// pub fn from_u8(value: u8) -> ProtoResult { match value { 1 => Ok(Self::SHA1), // TODO: where/when is SHA2? _ => Err(ProtoErrorKind::UnknownAlgorithmTypeValue(value).into()), } } /// ```text /// Laurie, et al. Standards Track [Page 14] /// /// RFC 5155 NSEC3 March 2008 /// /// Define H(x) to be the hash of x using the Hash Algorithm selected by /// the NSEC3 RR, k to be the number of Iterations, and || to indicate /// concatenation. Then define: /// /// IH(salt, x, 0) = H(x || salt), and /// /// IH(salt, x, k) = H(IH(salt, x, k-1) || salt), if k > 0 /// /// Then the calculated hash of an owner name is /// /// IH(salt, owner name, iterations), /// /// where the owner name is in the canonical form, defined as: /// /// The wire format of the owner name where: /// /// 1. The owner name is fully expanded (no DNS name compression) and /// fully qualified; /// /// 2. All uppercase US-ASCII letters are replaced by the corresponding /// lowercase US-ASCII letters; /// /// 3. If the owner name is a wildcard name, the owner name is in its /// original unexpanded form, including the "*" label (no wildcard /// substitution); /// ``` #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] pub fn hash(self, salt: &[u8], name: &Name, iterations: u16) -> ProtoResult { match self { // if there ever is more than just SHA1 support, this should be a genericized method Self::SHA1 => { let mut buf: Vec = Vec::new(); { let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut buf); encoder.set_canonical_names(true); name.emit(&mut encoder).expect("could not encode Name"); } Self::sha1_recursive_hash(salt, buf, iterations) } } } /// until there is another supported algorithm, just hardcoded to this. #[cfg(any(feature = "openssl", feature = "ring"))] fn sha1_recursive_hash(salt: &[u8], bytes: Vec, iterations: u16) -> ProtoResult { let digested: Digest; let to_digest = if iterations > 0 { digested = Self::sha1_recursive_hash(salt, bytes, iterations - 1)?; digested.as_ref() } else { &bytes }; DigestType::SHA1.digest_all(&[to_digest, salt]) } } impl From for u8 { fn from(a: Nsec3HashAlgorithm) -> Self { match a { Nsec3HashAlgorithm::SHA1 => 1, } } } #[test] #[cfg(any(feature = "openssl", feature = "ring"))] fn test_hash() { use std::str::FromStr; let name = Name::from_str("www.example.com").unwrap(); let salt: Vec = vec![1, 2, 3, 4]; assert_eq!( Nsec3HashAlgorithm::SHA1 .hash(&salt, &name, 0) .unwrap() .as_ref() .len(), 20 ); assert_eq!( Nsec3HashAlgorithm::SHA1 .hash(&salt, &name, 1) .unwrap() .as_ref() .len(), 20 ); assert_eq!( Nsec3HashAlgorithm::SHA1 .hash(&salt, &name, 3) .unwrap() .as_ref() .len(), 20 ); } #[test] #[cfg(any(feature = "openssl", feature = "ring"))] fn test_known_hashes() { // H(example) = 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom assert_eq!( hash_with_base32("example"), "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom" ); // H(a.example) = 35mthgpgcu1qg68fab165klnsnk3dpvl assert_eq!( hash_with_base32("a.example"), "35mthgpgcu1qg68fab165klnsnk3dpvl" ); // H(ai.example) = gjeqe526plbf1g8mklp59enfd789njgi assert_eq!( hash_with_base32("ai.example"), "gjeqe526plbf1g8mklp59enfd789njgi" ); // H(ns1.example) = 2t7b4g4vsa5smi47k61mv5bv1a22bojr assert_eq!( hash_with_base32("ns1.example"), "2t7b4g4vsa5smi47k61mv5bv1a22bojr" ); // H(ns2.example) = q04jkcevqvmu85r014c7dkba38o0ji5r assert_eq!( hash_with_base32("ns2.example"), "q04jkcevqvmu85r014c7dkba38o0ji5r" ); // H(w.example) = k8udemvp1j2f7eg6jebps17vp3n8i58h assert_eq!( hash_with_base32("w.example"), "k8udemvp1j2f7eg6jebps17vp3n8i58h" ); // H(*.w.example) = r53bq7cc2uvmubfu5ocmm6pers9tk9en assert_eq!( hash_with_base32("*.w.example"), "r53bq7cc2uvmubfu5ocmm6pers9tk9en" ); // H(x.w.example) = b4um86eghhds6nea196smvmlo4ors995 assert_eq!( hash_with_base32("x.w.example"), "b4um86eghhds6nea196smvmlo4ors995" ); // H(y.w.example) = ji6neoaepv8b5o6k4ev33abha8ht9fgc assert_eq!( hash_with_base32("y.w.example"), "ji6neoaepv8b5o6k4ev33abha8ht9fgc" ); // H(x.y.w.example) = 2vptu5timamqttgl4luu9kg21e0aor3s assert_eq!( hash_with_base32("x.y.w.example"), "2vptu5timamqttgl4luu9kg21e0aor3s" ); // H(xx.example) = t644ebqk9bibcna874givr6joj62mlhv assert_eq!( hash_with_base32("xx.example"), "t644ebqk9bibcna874givr6joj62mlhv" ); } #[cfg(test)] #[cfg(any(feature = "openssl", feature = "ring"))] fn hash_with_base32(name: &str) -> String { use data_encoding::BASE32_DNSSEC; // NSEC3PARAM 1 0 12 aabbccdd let known_name = Name::parse(name, Some(&Name::new())).unwrap(); let known_salt = [0xAAu8, 0xBBu8, 0xCCu8, 0xDDu8]; let hash = Nsec3HashAlgorithm::SHA1 .hash(&known_salt, &known_name, 12) .unwrap(); BASE32_DNSSEC.encode(hash.as_ref()) } hickory-proto-0.24.0/src/rr/dnssec/public_key.rs000064400000000000000000000531441046102023000177140ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Public Key implementations for supported key types #[cfg(not(any(feature = "openssl", feature = "ring")))] use std::marker::PhantomData; #[cfg(all(not(feature = "ring"), feature = "openssl"))] use openssl::bn::BigNum; #[cfg(all(not(feature = "ring"), feature = "openssl"))] use openssl::bn::BigNumContext; #[cfg(all(not(feature = "ring"), feature = "openssl"))] use openssl::ec::{EcGroup, EcKey, EcPoint}; #[cfg(all(not(feature = "ring"), feature = "openssl"))] use openssl::nid::Nid; #[cfg(all(not(feature = "ring"), feature = "openssl"))] use openssl::pkey::{PKey, Public}; #[cfg(all(not(feature = "ring"), feature = "openssl"))] use openssl::rsa::Rsa as OpenSslRsa; #[cfg(all(not(feature = "ring"), feature = "openssl"))] use openssl::sign::Verifier; #[cfg(feature = "ring")] use ring::signature::{self, ED25519_PUBLIC_KEY_LEN}; use crate::error::*; use crate::rr::dnssec::Algorithm; #[cfg(all(not(feature = "ring"), feature = "openssl"))] use crate::rr::dnssec::DigestType; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::rr::dnssec::ec_public_key::ECPublicKey; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::rr::dnssec::rsa_public_key::RSAPublicKey; /// PublicKeys implement the ability to ideally be zero copy abstractions over public keys for verifying signed content. /// /// In DNS the KEY and DNSKEY types are generally the RData types which store public key material. pub trait PublicKey { /// Returns the public bytes of the public key, in DNS format fn public_bytes(&self) -> &[u8]; /// Verifies the hash matches the signature with the current `key`. /// /// # Arguments /// /// * `message` - the message to be validated, see `hash_rrset` /// * `signature` - the signature to use to verify the hash, extracted from an `RData::RRSIG` /// for example. /// /// # Return value /// /// True if and only if the signature is valid for the hash. This will always return /// false if the `key`. #[allow(unused)] fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()>; } #[cfg(all(not(feature = "ring"), feature = "openssl"))] fn verify_with_pkey( pkey: &PKey, algorithm: Algorithm, message: &[u8], signature: &[u8], ) -> ProtoResult<()> { let digest_type = DigestType::from(algorithm).to_openssl_digest()?; let mut verifier = Verifier::new(digest_type, pkey)?; verifier.update(message)?; verifier .verify(signature) .map_err(Into::into) .and_then(|b| { if b { Ok(()) } else { Err("could not verify".into()) } }) } /// Elyptic Curve public key type #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] pub struct Ec<'k> { raw: &'k [u8], pkey: PKey, } #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] impl<'k> Ec<'k> { /// ```text /// RFC 6605 ECDSA for DNSSEC April 2012 /// /// 4. DNSKEY and RRSIG Resource Records for ECDSA /// /// ECDSA public keys consist of a single value, called "Q" in FIPS /// 186-3. In DNSSEC keys, Q is a simple bit string that represents the /// uncompressed form of a curve point, "x | y". /// /// The ECDSA signature is the combination of two non-negative integers, /// called "r" and "s" in FIPS 186-3. The two integers, each of which is /// formatted as a simple octet string, are combined into a single longer /// octet string for DNSSEC as the concatenation "r | s". (Conversion of /// the integers to bit strings is described in Section C.2 of FIPS /// 186-3.) For P-256, each integer MUST be encoded as 32 octets; for /// P-384, each integer MUST be encoded as 48 octets. /// /// The algorithm numbers associated with the DNSKEY and RRSIG resource /// records are fully defined in the IANA Considerations section. They /// are: /// /// o DNSKEY and RRSIG RRs signifying ECDSA with the P-256 curve and /// SHA-256 use the algorithm number 13. /// /// o DNSKEY and RRSIG RRs signifying ECDSA with the P-384 curve and /// SHA-384 use the algorithm number 14. /// /// Conformant implementations that create records to be put into the DNS /// MUST implement signing and verification for both of the above /// algorithms. Conformant DNSSEC verifiers MUST implement verification /// for both of the above algorithms. /// ``` pub fn from_public_bytes(public_key: &'k [u8], algorithm: Algorithm) -> ProtoResult { let curve = match algorithm { Algorithm::ECDSAP256SHA256 => Nid::X9_62_PRIME256V1, Algorithm::ECDSAP384SHA384 => Nid::SECP384R1, _ => return Err("only ECDSAP256SHA256 and ECDSAP384SHA384 are supported by Ec".into()), }; // Key needs to be converted to OpenSSL format let k = ECPublicKey::from_unprefixed(public_key, algorithm)?; EcGroup::from_curve_name(curve) .and_then(|group| BigNumContext::new().map(|ctx| (group, ctx))) // FYI: BigNum slices treat all slices as BigEndian, i.e NetworkByteOrder .and_then(|(group, mut ctx)| { EcPoint::from_bytes(&group, k.prefixed_bytes(), &mut ctx) .map(|point| (group, point)) }) .and_then(|(group, point)| EcKey::from_public_key(&group, &point)) .and_then(PKey::from_ec_key) .map_err(Into::into) .map(|pkey| Ec { raw: public_key, pkey, }) } } #[cfg(all(not(feature = "ring"), feature = "openssl"))] fn asn1_emit_integer(output: &mut Vec, int: &[u8]) { assert!(!int.is_empty()); output.push(0x02); // INTEGER if int[0] > 0x7f { output.push((int.len() + 1) as u8); output.push(0x00); // MSB must be zero output.extend(int); return; } // Trim leading zeros let mut pos = 0; while pos < int.len() { if int[pos] == 0 { if pos == int.len() - 1 { break; } pos += 1; continue; } if int[pos] > 0x7f { // We need to leave one 0x00 to make MSB zero pos -= 1; } break; } let int_output = &int[pos..]; output.push(int_output.len() as u8); output.extend(int_output); } /// Convert raw DNSSEC ECDSA signature to ASN.1 DER format #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] pub fn dnssec_ecdsa_signature_to_der(signature: &[u8]) -> ProtoResult> { if signature.is_empty() || signature.len() & 1 != 0 || signature.len() > 127 { return Err("invalid signature length".into()); } let part_len = signature.len() / 2; // ASN.1 SEQUENCE: 0x30 [LENGTH] let mut signature_asn1 = vec![0x30, 0x00]; asn1_emit_integer(&mut signature_asn1, &signature[..part_len]); asn1_emit_integer(&mut signature_asn1, &signature[part_len..]); signature_asn1[1] = (signature_asn1.len() - 2) as u8; Ok(signature_asn1) } #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] impl<'k> PublicKey for Ec<'k> { fn public_bytes(&self) -> &[u8] { self.raw } fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> { let signature_asn1 = dnssec_ecdsa_signature_to_der(signature)?; verify_with_pkey(&self.pkey, algorithm, message, &signature_asn1) } } /// Elyptic Curve public key type #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub type Ec = ECPublicKey; #[cfg(feature = "ring")] impl Ec { /// ```text /// RFC 6605 ECDSA for DNSSEC April 2012 /// /// 4. DNSKEY and RRSIG Resource Records for ECDSA /// /// ECDSA public keys consist of a single value, called "Q" in FIPS /// 186-3. In DNSSEC keys, Q is a simple bit string that represents the /// uncompressed form of a curve point, "x | y". /// /// The ECDSA signature is the combination of two non-negative integers, /// called "r" and "s" in FIPS 186-3. The two integers, each of which is /// formatted as a simple octet string, are combined into a single longer /// octet string for DNSSEC as the concatenation "r | s". (Conversion of /// the integers to bit strings is described in Section C.2 of FIPS /// 186-3.) For P-256, each integer MUST be encoded as 32 octets; for /// P-384, each integer MUST be encoded as 48 octets. /// /// The algorithm numbers associated with the DNSKEY and RRSIG resource /// records are fully defined in the IANA Considerations section. They /// are: /// /// o DNSKEY and RRSIG RRs signifying ECDSA with the P-256 curve and /// SHA-256 use the algorithm number 13. /// /// o DNSKEY and RRSIG RRs signifying ECDSA with the P-384 curve and /// SHA-384 use the algorithm number 14. /// /// Conformant implementations that create records to be put into the DNS /// MUST implement signing and verification for both of the above /// algorithms. Conformant DNSSEC verifiers MUST implement verification /// for both of the above algorithms. /// ``` pub fn from_public_bytes(public_key: &[u8], algorithm: Algorithm) -> ProtoResult { Self::from_unprefixed(public_key, algorithm) } } #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] impl PublicKey for Ec { fn public_bytes(&self) -> &[u8] { self.unprefixed_bytes() } fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> { // TODO: assert_eq!(algorithm, self.algorithm); once *ring* allows this. let alg = match algorithm { Algorithm::ECDSAP256SHA256 => &signature::ECDSA_P256_SHA256_FIXED, Algorithm::ECDSAP384SHA384 => &signature::ECDSA_P384_SHA384_FIXED, _ => return Err("only ECDSAP256SHA256 and ECDSAP384SHA384 are supported by Ec".into()), }; let public_key = signature::UnparsedPublicKey::new(alg, self.prefixed_bytes()); public_key.verify(message, signature).map_err(Into::into) } } /// Ed25519 Public key #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub struct Ed25519<'k> { raw: &'k [u8], } #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] impl<'k> Ed25519<'k> { /// ```text /// Internet-Draft EdDSA for DNSSEC December 2016 /// /// An Ed25519 public key consists of a 32-octet value, which is encoded /// into the Public Key field of a DNSKEY resource record as a simple bit /// string. The generation of a public key is defined in Section 5.1.5 /// in [RFC 8032]. Breaking tradition, the keys are encoded in little- /// endian byte order. /// ``` pub fn from_public_bytes(public_key: &'k [u8]) -> ProtoResult { if public_key.len() != ED25519_PUBLIC_KEY_LEN { return Err(format!( "expected {} byte public_key: {}", ED25519_PUBLIC_KEY_LEN, public_key.len() ) .into()); } Ok(Ed25519 { raw: public_key }) } } #[cfg(feature = "ring")] impl<'k> PublicKey for Ed25519<'k> { // TODO: just store reference to public key bytes in ctor... fn public_bytes(&self) -> &[u8] { self.raw } fn verify(&self, _: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> { let public_key = signature::UnparsedPublicKey::new(&signature::ED25519, self.raw); public_key.verify(message, signature).map_err(Into::into) } } /// Rsa public key #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] pub struct Rsa<'k> { raw: &'k [u8], #[cfg(all(not(feature = "ring"), feature = "openssl"))] pkey: PKey, #[cfg(feature = "ring")] pkey: RSAPublicKey<'k>, } #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] impl<'k> Rsa<'k> { /// ```text /// RFC 3110 RSA SIGs and KEYs in the DNS May 2001 /// /// 2. RSA Public KEY Resource Records /// /// RSA public keys are stored in the DNS as KEY RRs using algorithm /// number 5 [RFC2535]. The structure of the algorithm specific portion /// of the RDATA part of such RRs is as shown below. /// /// Field Size /// ----- ---- /// exponent length 1 or 3 octets (see text) /// exponent as specified by length field /// modulus remaining space /// /// For interoperability, the exponent and modulus are each limited to /// 4096 bits in length. The public key exponent is a variable length /// unsigned integer. Its length in octets is represented as one octet /// if it is in the range of 1 to 255 and by a zero octet followed by a /// two octet unsigned length if it is longer than 255 bytes. The public /// key modulus field is a multiprecision unsigned integer. The length /// of the modulus can be determined from the RDLENGTH and the preceding /// RDATA fields including the exponent. Leading zero octets are /// prohibited in the exponent and modulus. /// /// Note: KEY RRs for use with RSA/SHA1 DNS signatures MUST use this /// algorithm number (rather than the algorithm number specified in the /// obsoleted RFC 2537). /// /// Note: This changes the algorithm number for RSA KEY RRs to be the /// same as the new algorithm number for RSA/SHA1 SIGs. /// ``` pub fn from_public_bytes(raw: &'k [u8]) -> ProtoResult { let parsed = RSAPublicKey::try_from(raw)?; let pkey = into_pkey(parsed)?; Ok(Rsa { raw, pkey }) } } #[cfg(all(not(feature = "ring"), feature = "openssl"))] fn into_pkey(parsed: RSAPublicKey<'_>) -> ProtoResult> { // FYI: BigNum slices treat all slices as BigEndian, i.e NetworkByteOrder let e = BigNum::from_slice(parsed.e())?; let n = BigNum::from_slice(parsed.n())?; OpenSslRsa::from_public_components(n, e) .and_then(PKey::from_rsa) .map_err(Into::into) } #[cfg(feature = "ring")] #[allow(clippy::unnecessary_wraps)] fn into_pkey(parsed: RSAPublicKey<'_>) -> ProtoResult> { Ok(parsed) } #[cfg(any(feature = "openssl", feature = "ring"))] impl<'k> PublicKey for Rsa<'k> { fn public_bytes(&self) -> &[u8] { self.raw } #[cfg(all(not(feature = "ring"), feature = "openssl"))] fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> { verify_with_pkey(&self.pkey, algorithm, message, signature) } #[cfg(feature = "ring")] fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> { #[allow(deprecated)] let alg = match algorithm { Algorithm::RSASHA256 => &signature::RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY, Algorithm::RSASHA512 => &signature::RSA_PKCS1_1024_8192_SHA512_FOR_LEGACY_USE_ONLY, Algorithm::RSASHA1 => &signature::RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY, Algorithm::RSASHA1NSEC3SHA1 => { return Err("*ring* doesn't support RSASHA1NSEC3SHA1 yet".into()) } _ => unreachable!("non-RSA algorithm passed to RSA verify()"), }; let public_key = signature::RsaPublicKeyComponents { n: self.pkey.n(), e: self.pkey.e(), }; public_key .verify(alg, message, signature) .map_err(Into::into) } } /// Variants of all know public keys #[non_exhaustive] pub enum PublicKeyEnum<'k> { /// RSA keypair, supported by OpenSSL #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] Rsa(Rsa<'k>), /// Elliptic curve keypair #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(any(all(not(feature = "ring"), feature = "openssl")))))] Ec(Ec<'k>), /// Elliptic curve keypair #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] Ec(Ec), /// Ed25519 public key for the Algorithm::ED25519 #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] Ed25519(Ed25519<'k>), /// PhatomData for compiler when ring and or openssl not defined, do not use... #[cfg(not(any(feature = "ring", feature = "openssl")))] #[cfg_attr(docsrs, doc(cfg(not(any(feature = "ring", feature = "openssl")))))] Phantom(&'k PhantomData<()>), } impl<'k> PublicKeyEnum<'k> { /// Converts the bytes into a PulbicKey of the specified algorithm #[allow(unused_variables, clippy::match_single_binding)] pub fn from_public_bytes(public_key: &'k [u8], algorithm: Algorithm) -> ProtoResult { #[allow(deprecated)] match algorithm { #[cfg(any(feature = "openssl", feature = "ring"))] Algorithm::ECDSAP256SHA256 | Algorithm::ECDSAP384SHA384 => Ok(PublicKeyEnum::Ec( Ec::from_public_bytes(public_key, algorithm)?, )), #[cfg(feature = "ring")] Algorithm::ED25519 => Ok(PublicKeyEnum::Ed25519(Ed25519::from_public_bytes( public_key, )?)), #[cfg(any(feature = "openssl", feature = "ring"))] Algorithm::RSASHA1 | Algorithm::RSASHA1NSEC3SHA1 | Algorithm::RSASHA256 | Algorithm::RSASHA512 => Ok(PublicKeyEnum::Rsa(Rsa::from_public_bytes(public_key)?)), _ => Err("public key algorithm not supported".into()), } } } impl<'k> PublicKey for PublicKeyEnum<'k> { #[allow(clippy::match_single_binding, clippy::match_single_binding)] fn public_bytes(&self) -> &[u8] { match *self { #[cfg(any(feature = "openssl", feature = "ring"))] PublicKeyEnum::Ec(ref ec) => ec.public_bytes(), #[cfg(feature = "ring")] PublicKeyEnum::Ed25519(ref ed) => ed.public_bytes(), #[cfg(any(feature = "openssl", feature = "ring"))] PublicKeyEnum::Rsa(ref rsa) => rsa.public_bytes(), #[cfg(not(any(feature = "ring", feature = "openssl")))] _ => panic!("no public keys registered, enable ring or openssl features"), } } #[allow(unused_variables, clippy::match_single_binding)] fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> { match *self { #[cfg(any(feature = "openssl", feature = "ring"))] PublicKeyEnum::Ec(ref ec) => ec.verify(algorithm, message, signature), #[cfg(feature = "ring")] PublicKeyEnum::Ed25519(ref ed) => ed.verify(algorithm, message, signature), #[cfg(any(feature = "openssl", feature = "ring"))] PublicKeyEnum::Rsa(ref rsa) => rsa.verify(algorithm, message, signature), #[cfg(not(any(feature = "ring", feature = "openssl")))] _ => panic!("no public keys registered, enable ring or openssl features"), } } } /// An owned variant of PublicKey pub struct PublicKeyBuf { key_buf: Vec, } impl PublicKeyBuf { /// Constructs a new PublicKey from the specified bytes, these should be in DNSKEY form. pub fn new(key_buf: Vec) -> Self { Self { key_buf } } } impl PublicKey for PublicKeyBuf { fn public_bytes(&self) -> &[u8] { &self.key_buf } fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> ProtoResult<()> { let public_key = PublicKeyEnum::from_public_bytes(&self.key_buf, algorithm)?; public_key.verify(algorithm, message, signature) } } #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg(test)] mod tests { #[cfg(feature = "openssl")] #[test] fn test_asn1_emit_integer() { fn test_case(source: &[u8], expected_data: &[u8]) { use crate::rr::dnssec::public_key::asn1_emit_integer; let mut output = Vec::::new(); asn1_emit_integer(&mut output, source); assert_eq!(output[0], 0x02); assert_eq!(output[1], expected_data.len() as u8); assert_eq!(&output[2..], expected_data); } test_case(&[0x00], &[0x00]); test_case(&[0x00, 0x00], &[0x00]); test_case(&[0x7f], &[0x7f]); test_case(&[0x80], &[0x00, 0x80]); test_case(&[0x00, 0x80], &[0x00, 0x80]); test_case(&[0x00, 0x00, 0x80], &[0x00, 0x80]); test_case(&[0x7f, 0x00, 0x80], &[0x7f, 0x00, 0x80]); test_case(&[0x00, 0x7f, 0x00, 0x80], &[0x7f, 0x00, 0x80]); test_case(&[0x80, 0x00, 0x80], &[0x00, 0x80, 0x00, 0x80]); test_case(&[0xff, 0x00, 0x80], &[0x00, 0xff, 0x00, 0x80]); } } hickory-proto-0.24.0/src/rr/dnssec/rdata/cdnskey.rs000064400000000000000000000041421046102023000203130ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! CDNSKEY type and related implementations use std::{fmt, ops::Deref}; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::ProtoResult, rr::{RData, RecordData, RecordDataDecodable, RecordType}, serialize::binary::{BinDecoder, BinEncodable, BinEncoder, Restrict}, }; use super::{DNSSECRData, DNSKEY}; /// RRSIG is really a derivation of the original SIG record data. See SIG for more documentation #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct CDNSKEY(DNSKEY); impl Deref for CDNSKEY { type Target = DNSKEY; fn deref(&self) -> &Self::Target { &self.0 } } impl BinEncodable for CDNSKEY { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { self.0.emit(encoder) } } impl<'r> RecordDataDecodable<'r> for CDNSKEY { fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { DNSKEY::read_data(decoder, length).map(Self) } } impl RecordData for CDNSKEY { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::CDNSKEY(cdnskey)) => Ok(cdnskey), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::CDNSKEY(cdnskey)) => Some(cdnskey), _ => None, } } fn record_type(&self) -> RecordType { RecordType::CDNSKEY } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::CDNSKEY(self)) } } impl fmt::Display for CDNSKEY { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", self.0) } } hickory-proto-0.24.0/src/rr/dnssec/rdata/cds.rs000064400000000000000000000040261046102023000174250ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! CDS type and related implementations use std::{fmt, ops::Deref}; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::ProtoResult, rr::{RData, RecordData, RecordDataDecodable, RecordType}, serialize::binary::{BinDecoder, BinEncodable, BinEncoder, Restrict}, }; use super::{DNSSECRData, DS}; /// RRSIG is really a derivation of the original SIG record data. See SIG for more documentation #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct CDS(DS); impl Deref for CDS { type Target = DS; fn deref(&self) -> &Self::Target { &self.0 } } impl BinEncodable for CDS { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { self.0.emit(encoder) } } impl<'r> RecordDataDecodable<'r> for CDS { fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { DS::read_data(decoder, length).map(Self) } } impl RecordData for CDS { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::CDS(cds)) => Ok(cds), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::CDS(cds)) => Some(cds), _ => None, } } fn record_type(&self) -> RecordType { RecordType::CDS } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::CDS(self)) } } impl fmt::Display for CDS { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", self.0) } } hickory-proto-0.24.0/src/rr/dnssec/rdata/dnskey.rs000064400000000000000000000500101046102023000201430ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! public key record data for signing zone records use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::{ProtoError, ProtoErrorKind, ProtoResult}, rr::{ dnssec::{Algorithm, Digest, DigestType}, record_data::RData, Name, RecordData, RecordDataDecodable, RecordType, }, serialize::binary::{ BinDecodable, BinDecoder, BinEncodable, BinEncoder, Restrict, RestrictedMath, }, }; use super::DNSSECRData; /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-2), DNSSEC Resource Records, March 2005 /// /// ```text /// 2. The DNSKEY Resource Record /// /// DNSSEC uses public key cryptography to sign and authenticate DNS /// resource record sets (RRsets). The public keys are stored in DNSKEY /// resource records and are used in the DNSSEC authentication process /// described in [RFC4035]: A zone signs its authoritative RRsets by /// using a private key and stores the corresponding public key in a /// DNSKEY RR. A resolver can then use the public key to validate /// signatures covering the RRsets in the zone, and thus to authenticate /// them. /// /// The DNSKEY RR is not intended as a record for storing arbitrary /// public keys and MUST NOT be used to store certificates or public keys /// that do not directly relate to the DNS infrastructure. /// /// The Type value for the DNSKEY RR type is 48. /// /// The DNSKEY RR is class independent. /// /// The DNSKEY RR has no special TTL requirements. /// /// 2.1. DNSKEY RDATA Wire Format /// /// The RDATA for a DNSKEY RR consists of a 2 octet Flags Field, a 1 /// octet Protocol Field, a 1 octet Algorithm Field, and the Public Key /// Field. /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Flags | Protocol | Algorithm | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / / /// / Public Key / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// 2.1.5. Notes on DNSKEY RDATA Design /// /// Although the Protocol Field always has value 3, it is retained for /// backward compatibility with early versions of the KEY record. /// /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct DNSKEY { zone_key: bool, secure_entry_point: bool, revoke: bool, algorithm: Algorithm, public_key: Vec, } impl DNSKEY { /// Construct a new DNSKey RData /// /// # Arguments /// /// * `zone_key` - this key is used to sign Zone resource records /// * `secure_entry_point` - this key is used to sign DNSKeys that sign the Zone records /// * `revoke` - this key has been revoked /// * `algorithm` - specifies the algorithm which this Key uses to sign records /// * `public_key` - the public key material, in native endian, the emitter will perform any necessary conversion /// /// # Return /// /// A new DNSKEY RData for use in a Resource Record pub fn new( zone_key: bool, secure_entry_point: bool, revoke: bool, algorithm: Algorithm, public_key: Vec, ) -> Self { Self { zone_key, secure_entry_point, revoke, algorithm, public_key, } } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.1) /// /// ```text /// 2.1.1. The Flags Field /// /// Bit 7 of the Flags field is the Zone Key flag. If bit 7 has value 1, /// then the DNSKEY record holds a DNS zone key, and the DNSKEY RR's /// owner name MUST be the name of a zone. If bit 7 has value 0, then /// the DNSKEY record holds some other type of DNS public key and MUST /// NOT be used to verify RRSIGs that cover RRsets. /// /// /// Bits 0-6 and 8-14 are reserved: these bits MUST have value 0 upon /// creation of the DNSKEY RR and MUST be ignored upon receipt. /// ``` pub fn zone_key(&self) -> bool { self.zone_key } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.1) /// /// ```text /// 2.1.1. The Flags Field /// /// Bit 15 of the Flags field is the Secure Entry Point flag, described /// in [RFC3757]. If bit 15 has value 1, then the DNSKEY record holds a /// key intended for use as a secure entry point. This flag is only /// intended to be a hint to zone signing or debugging software as to the /// intended use of this DNSKEY record; validators MUST NOT alter their /// behavior during the signature validation process in any way based on /// the setting of this bit. This also means that a DNSKEY RR with the /// SEP bit set would also need the Zone Key flag set in order to be able /// to generate signatures legally. A DNSKEY RR with the SEP set and the /// Zone Key flag not set MUST NOT be used to verify RRSIGs that cover /// RRsets. /// ``` pub fn secure_entry_point(&self) -> bool { self.secure_entry_point } /// [RFC 5011, Trust Anchor Update, September 2007](https://tools.ietf.org/html/rfc5011#section-3) /// /// ```text /// RFC 5011 Trust Anchor Update September 2007 /// /// 7. IANA Considerations /// /// The IANA has assigned a bit in the DNSKEY flags field (see Section 7 /// of [RFC4034]) for the REVOKE bit (8). /// ``` pub fn revoke(&self) -> bool { self.revoke } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.3) /// /// ```text /// 2.1.3. The Algorithm Field /// /// The Algorithm field identifies the public key's cryptographic /// algorithm and determines the format of the Public Key field. A list /// of DNSSEC algorithm types can be found in Appendix A.1 /// ``` pub fn algorithm(&self) -> Algorithm { self.algorithm } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.4) /// /// ```text /// 2.1.4. The Public Key Field /// /// The Public Key Field holds the public key material. The format /// depends on the algorithm of the key being stored and is described in /// separate documents. /// ``` pub fn public_key(&self) -> &[u8] { &self.public_key } /// Output the encoded form of the flags pub fn flags(&self) -> u16 { let mut flags: u16 = 0; if self.zone_key() { flags |= 0b0000_0001_0000_0000 } if self.secure_entry_point() { flags |= 0b0000_0000_0000_0001 } if self.revoke() { flags |= 0b0000_0000_1000_0000 } flags } /// Creates a message digest for this DNSKEY record. /// /// ```text /// 5.1.4. The Digest Field /// /// The DS record refers to a DNSKEY RR by including a digest of that /// DNSKEY RR. /// /// The digest is calculated by concatenating the canonical form of the /// fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA, /// and then applying the digest algorithm. /// /// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); /// /// "|" denotes concatenation /// /// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. /// /// The size of the digest may vary depending on the digest algorithm and /// DNSKEY RR size. As of the time of this writing, the only defined /// digest algorithm is SHA-1, which produces a 20 octet digest. /// ``` /// /// # Arguments /// /// * `name` - the label of of the DNSKEY record. /// * `digest_type` - the `DigestType` with which to create the message digest. #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] pub fn to_digest(&self, name: &Name, digest_type: DigestType) -> ProtoResult { let mut buf: Vec = Vec::new(); { let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut buf); encoder.set_canonical_names(true); if let Err(e) = name .emit(&mut encoder) .and_then(|_| self.emit(&mut encoder)) { tracing::warn!("error serializing dnskey: {e}"); return Err(format!("error serializing dnskey: {e}").into()); } } digest_type.hash(&buf) } /// This will always return an error unless the Ring or OpenSSL features are enabled #[cfg(not(any(feature = "openssl", feature = "ring")))] #[cfg_attr(docsrs, doc(cfg(not(any(feature = "openssl", feature = "ring")))))] pub fn to_digest(&self, _: &Name, _: DigestType) -> ProtoResult { Err("Ring or OpenSSL must be enabled for this feature".into()) } /// The key tag is calculated as a hash to more quickly lookup a DNSKEY. /// /// [RFC 2535](https://tools.ietf.org/html/rfc2535), Domain Name System Security Extensions, March 1999 /// /// ```text /// RFC 2535 DNS Security Extensions March 1999 /// /// 4.1.6 Key Tag Field /// /// The "key Tag" is a two octet quantity that is used to efficiently /// select between multiple keys which may be applicable and thus check /// that a public key about to be used for the computationally expensive /// effort to check the signature is possibly valid. For algorithm 1 /// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two /// octets of the public key modulus needed to decode the signature /// field. That is to say, the most significant 16 of the least /// significant 24 bits of the modulus in network (big endian) order. For /// all other algorithms, including private algorithms, it is calculated /// as a simple checksum of the KEY RR as described in Appendix C. /// /// Appendix C: Key Tag Calculation /// /// The key tag field in the SIG RR is just a means of more efficiently /// selecting the correct KEY RR to use when there is more than one KEY /// RR candidate available, for example, in verifying a signature. It is /// possible for more than one candidate key to have the same tag, in /// which case each must be tried until one works or all fail. The /// following reference implementation of how to calculate the Key Tag, /// for all algorithms other than algorithm 1, is in ANSI C. It is coded /// for clarity, not efficiency. (See section 4.1.6 for how to determine /// the Key Tag of an algorithm 1 key.) /// /// /* assumes int is at least 16 bits /// first byte of the key tag is the most significant byte of return /// value /// second byte of the key tag is the least significant byte of /// return value /// */ /// /// int keytag ( /// /// unsigned char key[], /* the RDATA part of the KEY RR */ /// unsigned int keysize, /* the RDLENGTH */ /// ) /// { /// long int ac; /* assumed to be 32 bits or larger */ /// /// for ( ac = 0, i = 0; i < keysize; ++i ) /// ac += (i&1) ? key[i] : key[i]<<8; /// ac += (ac>>16) & 0xFFFF; /// return ac & 0xFFFF; /// } /// ``` pub fn calculate_key_tag(&self) -> ProtoResult { // TODO: let mut bytes: Vec = Vec::with_capacity(512); { let mut e = BinEncoder::new(&mut bytes); self.emit(&mut e)?; } Ok(Self::calculate_key_tag_internal(&bytes)) } /// Internal checksum function (used for non-RSAMD5 hashes only, /// however, RSAMD5 is considered deprecated and not implemented in /// hickory-dns, anyways). pub fn calculate_key_tag_internal(bytes: &[u8]) -> u16 { let mut ac: u32 = 0; for (i, k) in bytes.iter().enumerate() { ac += u32::from(*k) << if i & 0x01 != 0 { 0 } else { 8 }; } ac += ac >> 16; (ac & 0xFFFF) as u16 } } impl From for RData { fn from(key: DNSKEY) -> Self { Self::DNSSEC(super::DNSSECRData::DNSKEY(key)) } } impl BinEncodable for DNSKEY { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.emit_u16(self.flags())?; encoder.emit(3)?; // always 3 for now self.algorithm().emit(encoder)?; encoder.emit_vec(self.public_key())?; Ok(()) } } impl<'r> RecordDataDecodable<'r> for DNSKEY { fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { let flags: u16 = decoder.read_u16()?.unverified(/*used as a bitfield, this is safe*/); // Bits 0-6 and 8-14 are reserved: these bits MUST have value 0 upon // creation of the DNSKEY RR and MUST be ignored upon receipt. let zone_key: bool = flags & 0b0000_0001_0000_0000 == 0b0000_0001_0000_0000; let secure_entry_point: bool = flags & 0b0000_0000_0000_0001 == 0b0000_0000_0000_0001; let revoke: bool = flags & 0b0000_0000_1000_0000 == 0b0000_0000_1000_0000; let _protocol: u8 = decoder .read_u8()? .verify_unwrap(|protocol| { // RFC 4034 DNSSEC Resource Records March 2005 // // 2.1.2. The Protocol Field // // The Protocol Field MUST have value 3, and the DNSKEY RR MUST be // treated as invalid during signature verification if it is found to be // some value other than 3. // // protocol is defined to only be '3' right now *protocol == 3 }) .map_err(|protocol| ProtoError::from(ProtoErrorKind::DnsKeyProtocolNot3(protocol)))?; let algorithm: Algorithm = Algorithm::read(decoder)?; // the public key is the left-over bytes minus 4 for the first fields // this sub is safe, as the first 4 fields must have been in the rdata, otherwise there would have been // an earlier return. let key_len = length .map(|u| u as usize) .checked_sub(4) .map_err(|_| ProtoError::from("invalid rdata length in DNSKEY"))? .unverified(/*used only as length safely*/); let public_key: Vec = decoder.read_vec(key_len)?.unverified(/*the byte array will fail in usage if invalid*/); Ok(Self::new( zone_key, secure_entry_point, revoke, algorithm, public_key, )) } } impl RecordData for DNSKEY { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::DNSKEY(csync)) => Ok(csync), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::DNSKEY(csync)) => Some(csync), _ => None, } } fn record_type(&self) -> RecordType { RecordType::DNSKEY } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::DNSKEY(self)) } } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.2) /// /// ```text /// 2.2. The DNSKEY RR Presentation Format /// /// The presentation format of the RDATA portion is as follows: /// /// The Flag field MUST be represented as an unsigned decimal integer. /// Given the currently defined flags, the possible values are: 0, 256, /// and 257. /// /// The Protocol Field MUST be represented as an unsigned decimal integer /// with a value of 3. /// /// The Algorithm field MUST be represented either as an unsigned decimal /// integer or as an algorithm mnemonic as specified in Appendix A.1. /// /// The Public Key field MUST be represented as a Base64 encoding of the /// Public Key. Whitespace is allowed within the Base64 text. For a /// definition of Base64 encoding, see [RFC3548]. /// /// 2.3. DNSKEY RR Example /// /// The following DNSKEY RR stores a DNS zone key for example.com. /// /// example.com. 86400 IN DNSKEY 256 3 5 ( AQPSKmynfzW4kyBv015MUG2DeIQ3 /// Cbl+BBZH4b/0PY1kxkmvHjcZc8no /// kfzj31GajIQKY+5CptLr3buXA10h /// WqTkF7H6RfoRqXQeogmMHfpftf6z /// Mv1LyBUgia7za6ZEzOJBOztyvhjL /// 742iU/TpPSEDhm2SNKLijfUppn1U /// aNvv4w== ) /// /// The first four text fields specify the owner name, TTL, Class, and RR /// type (DNSKEY). Value 256 indicates that the Zone Key bit (bit 7) in /// the Flags field has value 1. Value 3 is the fixed Protocol value. /// Value 5 indicates the public key algorithm. Appendix A.1 identifies /// algorithm type 5 as RSA/SHA1 and indicates that the format of the /// RSA/SHA1 public key field is defined in [RFC3110]. The remaining /// text is a Base64 encoding of the public key. /// ``` impl fmt::Display for DNSKEY { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, "{flags} 3 {alg} {key}", flags = self.flags(), alg = u8::from(self.algorithm), key = data_encoding::BASE64.encode(&self.public_key) ) } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; #[test] #[cfg(any(feature = "openssl", feature = "ring"))] fn test() { let rdata = DNSKEY::new( true, true, false, Algorithm::RSASHA256, vec![0, 1, 2, 3, 4, 5, 6, 7], ); let mut bytes = Vec::new(); let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); assert!(rdata.emit(&mut encoder).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {bytes:?}"); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let read_rdata = DNSKEY::read_data(&mut decoder, Restrict::new(bytes.len() as u16)); let read_rdata = read_rdata.expect("error decoding"); assert_eq!(rdata, read_rdata); assert!(rdata .to_digest( &Name::parse("www.example.com.", None).unwrap(), DigestType::SHA256 ) .is_ok()); } #[test] fn test_calculate_key_tag_checksum() { let test_text = "The quick brown fox jumps over the lazy dog"; let test_vectors = vec![ (vec![], 0), (vec![0, 0, 0, 0], 0), (vec![0xff, 0xff, 0xff, 0xff], 0xffff), (vec![1, 0, 0, 0], 0x0100), (vec![0, 1, 0, 0], 0x0001), (vec![0, 0, 1, 0], 0x0100), (test_text.as_bytes().to_vec(), 0x8d5b), ]; for &(ref input_data, exp_result) in test_vectors.iter() { let result = DNSKEY::calculate_key_tag_internal(input_data); assert_eq!(result, exp_result); } } } hickory-proto-0.24.0/src/rr/dnssec/rdata/ds.rs000064400000000000000000000311211046102023000172560ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! pointer record from parent zone to child zone for dnskey proof use std::fmt::{self, Display, Formatter}; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::{ProtoError, ProtoResult}, rr::{ dnssec::{rdata::DNSKEY, Algorithm, DigestType}, Name, RData, RecordData, RecordDataDecodable, RecordType, }, serialize::binary::*, }; use super::DNSSECRData; /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5) /// /// ```text /// 5.1. DS RDATA Wire Format /// /// The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet /// Algorithm field, a 1 octet Digest Type field, and a Digest field. /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Key Tag | Algorithm | Digest Type | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / / /// / Digest / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// 5.2. Processing of DS RRs When Validating Responses /// /// The DS RR links the authentication chain across zone boundaries, so /// the DS RR requires extra care in processing. The DNSKEY RR referred /// to in the DS RR MUST be a DNSSEC zone key. The DNSKEY RR Flags MUST /// have Flags bit 7 set. If the DNSKEY flags do not indicate a DNSSEC /// zone key, the DS RR (and the DNSKEY RR it references) MUST NOT be /// used in the validation process. /// /// 5.3. The DS RR Presentation Format /// /// The presentation format of the RDATA portion is as follows: /// /// The Key Tag field MUST be represented as an unsigned decimal integer. /// /// The Algorithm field MUST be represented either as an unsigned decimal /// integer or as an algorithm mnemonic specified in Appendix A.1. /// /// The Digest Type field MUST be represented as an unsigned decimal /// integer. /// /// The Digest MUST be represented as a sequence of case-insensitive /// hexadecimal digits. Whitespace is allowed within the hexadecimal /// text. /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct DS { key_tag: u16, algorithm: Algorithm, digest_type: DigestType, digest: Vec, } impl DS { /// Constructs a new DS RData /// /// # Arguments /// /// * `key_tag` - the key_tag associated to the DNSKEY /// * `algorithm` - algorithm as specified in the DNSKEY /// * `digest_type` - hash algorithm used to validate the DNSKEY /// * `digest` - hash of the DNSKEY /// /// # Returns /// /// the DS RDATA for use in a Resource Record pub fn new( key_tag: u16, algorithm: Algorithm, digest_type: DigestType, digest: Vec, ) -> Self { Self { key_tag, algorithm, digest_type, digest, } } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1) /// /// ```text /// 5.1.1. The Key Tag Field /// /// The Key Tag field lists the key tag of the DNSKEY RR referred to by /// the DS record, in network byte order. /// /// The Key Tag used by the DS RR is identical to the Key Tag used by /// RRSIG RRs. Appendix B describes how to compute a Key Tag. /// ``` pub fn key_tag(&self) -> u16 { self.key_tag } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1) /// /// ```text /// 5.1.2. The Algorithm Field /// /// The Algorithm field lists the algorithm number of the DNSKEY RR /// referred to by the DS record. /// /// The algorithm number used by the DS RR is identical to the algorithm /// number used by RRSIG and DNSKEY RRs. Appendix A.1 lists the /// algorithm number types. /// ``` pub fn algorithm(&self) -> Algorithm { self.algorithm } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1) /// /// ```text /// 5.1.3. The Digest Type Field /// /// The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY /// RR. The Digest Type field identifies the algorithm used to construct /// the digest. Appendix A.2 lists the possible digest algorithm types. /// ``` pub fn digest_type(&self) -> DigestType { self.digest_type } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.1.1) /// /// ```text /// 5.1.4. The Digest Field /// /// The DS record refers to a DNSKEY RR by including a digest of that /// DNSKEY RR. /// /// The digest is calculated by concatenating the canonical form of the /// fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA, /// and then applying the digest algorithm. /// /// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); /// /// "|" denotes concatenation /// /// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. /// /// The size of the digest may vary depending on the digest algorithm and /// DNSKEY RR size. As of the time of this writing, the only defined /// digest algorithm is SHA-1, which produces a 20 octet digest. /// ``` pub fn digest(&self) -> &[u8] { &self.digest } /// Validates that a given DNSKEY is covered by the DS record. /// /// # Return /// /// true if and only if the DNSKEY is covered by the DS record. #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] pub fn covers(&self, name: &Name, key: &DNSKEY) -> ProtoResult { key.to_digest(name, self.digest_type()) .map(|hash| hash.as_ref() == self.digest()) } /// This will always return an error unless the Ring or OpenSSL features are enabled #[cfg(not(any(feature = "openssl", feature = "ring")))] #[cfg_attr(docsrs, doc(cfg(not(any(feature = "openssl", feature = "ring")))))] pub fn covers(&self, _: &Name, _: &DNSKEY) -> ProtoResult { Err("Ring or OpenSSL must be enabled for this feature".into()) } } impl BinEncodable for DS { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.emit_u16(self.key_tag())?; self.algorithm().emit(encoder)?; // always 3 for now encoder.emit(self.digest_type().into())?; encoder.emit_vec(self.digest())?; Ok(()) } } impl<'r> RecordDataDecodable<'r> for DS { fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { let start_idx = decoder.index(); let key_tag: u16 = decoder.read_u16()?.unverified(/*key_tag is valid as any u16*/); let algorithm: Algorithm = Algorithm::read(decoder)?; let digest_type: DigestType = DigestType::from_u8(decoder.read_u8()?.unverified(/*DigestType is verified as safe*/))?; let bytes_read = decoder.index() - start_idx; let left: usize = length .map(|u| u as usize) .checked_sub(bytes_read) .map_err(|_| ProtoError::from("invalid rdata length in DS"))? .unverified(/*used only as length safely*/); let digest = decoder.read_vec(left)?.unverified(/*the byte array will fail in usage if invalid*/); Ok(Self::new(key_tag, algorithm, digest_type, digest)) } } impl RecordData for DS { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::DS(csync)) => Ok(csync), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::DS(csync)) => Some(csync), _ => None, } } fn record_type(&self) -> RecordType { RecordType::DS } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::DS(self)) } } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-5.3) /// /// ```text /// 5.3. The DS RR Presentation Format /// /// The presentation format of the RDATA portion is as follows: /// /// The Key Tag field MUST be represented as an unsigned decimal integer. /// /// The Algorithm field MUST be represented either as an unsigned decimal /// integer or as an algorithm mnemonic specified in Appendix A.1. /// /// The Digest Type field MUST be represented as an unsigned decimal /// integer. /// /// The Digest MUST be represented as a sequence of case-insensitive /// hexadecimal digits. Whitespace is allowed within the hexadecimal /// text. /// /// 5.4. DS RR Example /// /// The following example shows a DNSKEY RR and its corresponding DS RR. /// /// dskey.example.com. 86400 IN DNSKEY 256 3 5 ( AQOeiiR0GOMYkDshWoSKz9Xz /// fwJr1AYtsmx3TGkJaNXVbfi/ /// 2pHm822aJ5iI9BMzNXxeYCmZ /// DRD99WYwYqUSdjMmmAphXdvx /// egXd/M5+X7OrzKBaMbCVdFLU /// Uh6DhweJBjEVv5f2wwjM9Xzc /// nOf+EPbtG9DMBmADjFDc2w/r /// ljwvFw== /// ) ; key id = 60485 /// /// dskey.example.com. 86400 IN DS 60485 5 1 ( 2BB183AF5F22588179A53B0A /// 98631FAD1A292118 ) /// /// The first four text fields specify the name, TTL, Class, and RR type /// (DS). Value 60485 is the key tag for the corresponding /// "dskey.example.com." DNSKEY RR, and value 5 denotes the algorithm /// used by this "dskey.example.com." DNSKEY RR. The value 1 is the /// algorithm used to construct the digest, and the rest of the RDATA /// text is the digest in hexadecimal. /// ``` impl Display for DS { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { write!( f, "{tag} {alg} {ty} {digest}", tag = self.key_tag, alg = u8::from(self.algorithm), ty = u8::from(self.digest_type), digest = data_encoding::HEXUPPER_PERMISSIVE.encode(&self.digest) ) } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; #[test] fn test() { let rdata = DS::new( 0xF00F, Algorithm::RSASHA256, DigestType::SHA256, vec![5, 6, 7, 8], ); let mut bytes = Vec::new(); let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); assert!(rdata.emit(&mut encoder).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {bytes:?}"); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let restrict = Restrict::new(bytes.len() as u16); let read_rdata = DS::read_data(&mut decoder, restrict).expect("Decoding error"); assert_eq!(rdata, read_rdata); } #[test] #[cfg(any(feature = "openssl", feature = "ring"))] pub(crate) fn test_covers() { use crate::rr::dnssec::rdata::DNSKEY; let name = Name::parse("www.example.com.", None).unwrap(); let dnskey_rdata = DNSKEY::new(true, true, false, Algorithm::RSASHA256, vec![1, 2, 3, 4]); let ds_rdata = DS::new( 0, Algorithm::RSASHA256, DigestType::SHA256, dnskey_rdata .to_digest(&name, DigestType::SHA256) .unwrap() .as_ref() .to_owned(), ); assert!(ds_rdata.covers(&name, &dnskey_rdata).unwrap()); } } hickory-proto-0.24.0/src/rr/dnssec/rdata/key.rs000064400000000000000000001100211046102023000174350ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! public key record data for signing zone records #![allow(clippy::use_self)] use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::{ProtoError, ProtoResult}, rr::{dnssec::Algorithm, record_data::RData, RecordData, RecordDataDecodable, RecordType}, serialize::binary::*, }; use super::DNSSECRData; /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-3), Domain Name System Security Extensions, March 1999 /// /// ```text /// 3. The KEY Resource Record /// /// The KEY resource record (RR) is used to store a public key that is /// associated with a Domain Name System (DNS) name. This can be the /// public key of a zone, a user, or a host or other end entity. Security /// aware DNS implementations MUST be designed to handle at least two /// simultaneously valid keys of the same type associated with the same /// name. /// /// The type number for the KEY RR is 25. /// /// A KEY RR is, like any other RR, authenticated by a SIG RR. KEY RRs /// must be signed by a zone level key. /// /// 3.1 KEY RDATA format /// /// The RDATA for a KEY RR consists of flags, a protocol octet, the /// algorithm number octet, and the public key itself. The format is as /// follows: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | flags | protocol | algorithm | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | / /// / public key / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| /// /// The KEY RR is not intended for storage of certificates and a separate /// certificate RR has been developed for that purpose, defined in [RFC /// 2538]. /// /// The meaning of the KEY RR owner name, flags, and protocol octet are /// described in Sections 3.1.1 through 3.1.5 below. The flags and /// algorithm must be examined before any data following the algorithm /// octet as they control the existence and format of any following data. /// The algorithm and public key fields are described in Section 3.2. /// The format of the public key is algorithm dependent. /// /// KEY RRs do not specify their validity period but their authenticating /// SIG RR(s) do as described in Section 4 below. /// /// 3.1.1 Object Types, DNS Names, and Keys /// /// The public key in a KEY RR is for the object named in the owner name. /// /// A DNS name may refer to three different categories of things. For /// example, foo.host.example could be (1) a zone, (2) a host or other /// end entity , or (3) the mapping into a DNS name of the user or /// account foo@host.example. Thus, there are flag bits, as described /// below, in the KEY RR to indicate with which of these roles the owner /// name and public key are associated. Note that an appropriate zone /// KEY RR MUST occur at the apex node of a secure zone and zone KEY RRs /// occur only at delegation points. /// /// 3.1.2 The KEY RR Flag Field /// /// In the "flags" field: /// /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 /// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ /// | A/C | Z | XT| Z | Z | NAMTYP| Z | Z | Z | Z | SIG | /// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ /// /// Bit 0 and 1 are the key "type" bits whose values have the following /// meanings: /// /// 10: Use of the key is prohibited for authentication. /// 01: Use of the key is prohibited for confidentiality. /// 00: Use of the key for authentication and/or confidentiality /// is permitted. Note that DNS security makes use of keys /// for authentication only. Confidentiality use flagging is /// provided for use of keys in other protocols. /// Implementations not intended to support key distribution /// for confidentiality MAY require that the confidentiality /// use prohibited bit be on for keys they serve. /// 11: If both bits are one, the "no key" value, there is no key /// information and the RR stops after the algorithm octet. /// By the use of this "no key" value, a signed KEY RR can /// authentically assert that, for example, a zone is not /// secured. See section 3.4 below. /// /// Bits 2 is reserved and must be zero. /// /// Bits 3 is reserved as a flag extension bit. If it is a one, a second /// 16 bit flag field is added after the algorithm octet and /// before the key data. This bit MUST NOT be set unless one or /// more such additional bits have been defined and are non-zero. /// /// Bits 4-5 are reserved and must be zero. /// /// Bits 6 and 7 form a field that encodes the name type. Field values /// have the following meanings: /// /// 00: indicates that this is a key associated with a "user" or /// "account" at an end entity, usually a host. The coding /// of the owner name is that used for the responsible /// individual mailbox in the SOA and RP RRs: The owner name /// is the user name as the name of a node under the entity /// name. For example, "j_random_user" on /// host.subdomain.example could have a public key associated /// through a KEY RR with name /// j_random_user.host.subdomain.example. It could be used /// in a security protocol where authentication of a user was /// desired. This key might be useful in IP or other /// security for a user level service such a telnet, ftp, /// rlogin, etc. /// 01: indicates that this is a zone key for the zone whose name /// is the KEY RR owner name. This is the public key used /// for the primary DNS security feature of data origin /// authentication. Zone KEY RRs occur only at delegation /// points. /// 10: indicates that this is a key associated with the non-zone /// "entity" whose name is the RR owner name. This will /// commonly be a host but could, in some parts of the DNS /// tree, be some other type of entity such as a telephone /// number [RFC 1530] or numeric IP address. This is the /// public key used in connection with DNS request and /// transaction authentication services. It could also be /// used in an IP-security protocol where authentication at /// the host, rather than user, level was desired, such as /// routing, NTP, etc. /// 11: reserved. /// /// Bits 8-11 are reserved and must be zero. /// /// Bits 12-15 are the "signatory" field. If non-zero, they indicate /// that the key can validly sign things as specified in DNS /// dynamic update [RFC 2137]. Note that zone keys (see bits /// 6 and 7 above) always have authority to sign any RRs in /// the zone regardless of the value of the signatory field. /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct KEY { key_trust: KeyTrust, key_usage: KeyUsage, signatory: UpdateScope, protocol: Protocol, algorithm: Algorithm, public_key: Vec, } /// Specifies in what contexts this key may be trusted for use #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum KeyTrust { /// Use of the key is prohibited for authentication NotAuth, /// Use of the key is prohibited for confidentiality NotPrivate, /// Use of the key for authentication and/or confidentiality is permitted AuthOrPrivate, /// If both bits are one, the "no key" value, (revocation?) DoNotTrust, } impl Default for KeyTrust { fn default() -> Self { Self::AuthOrPrivate } } impl From for KeyTrust { fn from(flags: u16) -> Self { // we only care about the first two bits, zero out the rest match flags & 0b1100_0000_0000_0000 { // 10: Use of the key is prohibited for authentication. 0b1000_0000_0000_0000 => Self::NotAuth, // 01: Use of the key is prohibited for confidentiality. 0b0100_0000_0000_0000 => Self::NotPrivate, // 00: Use of the key for authentication and/or confidentiality 0b0000_0000_0000_0000 => Self::AuthOrPrivate, // 11: If both bits are one, the "no key" value, there is no key 0b1100_0000_0000_0000 => Self::DoNotTrust, _ => panic!("All other bit fields should have been cleared"), } } } impl From for u16 { fn from(key_trust: KeyTrust) -> Self { match key_trust { // 10: Use of the key is prohibited for authentication. KeyTrust::NotAuth => 0b1000_0000_0000_0000, // 01: Use of the key is prohibited for confidentiality. KeyTrust::NotPrivate => 0b0100_0000_0000_0000, // 00: Use of the key for authentication and/or confidentiality KeyTrust::AuthOrPrivate => 0b0000_0000_0000_0000, // 11: If both bits are one, the "no key" value, there is no key KeyTrust::DoNotTrust => 0b1100_0000_0000_0000, } } } #[test] fn test_key_trust() { assert_eq!( KeyTrust::NotAuth, KeyTrust::from(u16::from(KeyTrust::NotAuth)) ); assert_eq!( KeyTrust::NotPrivate, KeyTrust::from(u16::from(KeyTrust::NotPrivate)) ); assert_eq!( KeyTrust::AuthOrPrivate, KeyTrust::from(u16::from(KeyTrust::AuthOrPrivate)) ); assert_eq!( KeyTrust::DoNotTrust, KeyTrust::from(u16::from(KeyTrust::DoNotTrust)) ); } /// Declares what this key is for #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] pub enum KeyUsage { /// key associated with a "user" or "account" at an end entity, usually a host Host, /// zone key for the zone whose name is the KEY RR owner name #[deprecated = "For Zone signing DNSKEY should be used"] Zone, /// associated with the non-zone "entity" whose name is the RR owner name Entity, /// Reserved Reserved, } impl Default for KeyUsage { fn default() -> Self { Self::Entity } } impl From for KeyUsage { fn from(flags: u16) -> Self { // we only care about the 6&7 two bits, zero out the rest match flags & 0b0000_0011_0000_0000 { // 00: indicates that this is a key associated with a "user" or 0b0000_0000_0000_0000 => Self::Host, // 01: indicates that this is a zone key for the zone whose name 0b0000_0001_0000_0000 => Self::Zone, // 10: indicates that this is a key associated with the non-zone 0b0000_0010_0000_0000 => Self::Entity, // 11: reserved. 0b0000_0011_0000_0000 => Self::Reserved, _ => panic!("All other bit fields should have been cleared"), } } } impl From for u16 { fn from(key_usage: KeyUsage) -> Self { match key_usage { // 00: indicates that this is a key associated with a "user" or KeyUsage::Host => 0b0000_0000_0000_0000, // 01: indicates that this is a zone key for the zone whose name KeyUsage::Zone => 0b0000_0001_0000_0000, // 10: indicates that this is a key associated with the non-zone KeyUsage::Entity => 0b0000_0010_0000_0000, // 11: reserved. KeyUsage::Reserved => 0b0000_0011_0000_0000, } } } #[test] fn test_key_usage() { assert_eq!(KeyUsage::Host, KeyUsage::from(u16::from(KeyUsage::Host))); assert_eq!(KeyUsage::Zone, KeyUsage::from(u16::from(KeyUsage::Zone))); assert_eq!( KeyUsage::Entity, KeyUsage::from(u16::from(KeyUsage::Entity)) ); assert_eq!( KeyUsage::Reserved, KeyUsage::from(u16::from(KeyUsage::Reserved)) ); } /// [RFC 2137](https://tools.ietf.org/html/rfc2137#section-3.1), Secure Domain Name System Dynamic Update, April 1997 /// /// ```text /// 3.1.1 Update Key Name Scope /// /// The owner name of any update authorizing KEY RR must (1) be the same /// as the owner name of any RRs being added or deleted or (2) a wildcard /// name including within its extended scope (see section 3.3) the name /// of any RRs being added or deleted and those RRs must be in the same /// zone. /// /// 3.1.2 Update Key Class Scope /// /// The class of any update authorizing KEY RR must be the same as the /// class of any RR's being added or deleted. /// /// 3.1.3 Update Key Signatory Field /// /// The four bit "signatory field" (see RFC 2065) of any update /// authorizing KEY RR must be non-zero. The bits have the meanings /// described below for non-zone keys (see section 3.2 for zone type /// keys). /// /// UPDATE KEY RR SIGNATORY FIELD BITS /// /// 0 1 2 3 /// +-----------+-----------+-----------+-----------+ /// | zone | strong | unique | general | /// +-----------+-----------+-----------+-----------+ /// /// Bit 0, zone control - If nonzero, this key is authorized to attach, /// detach, and move zones by creating and deleting NS, glue A, and /// zone KEY RR(s). If zero, the key can not authorize any update /// that would effect such RRs. This bit is meaningful for both /// type A and type B dynamic secure zones. /// /// NOTE: do not confuse the "zone" signatory field bit with the /// "zone" key type bit. /// /// Bit 1, strong update - If nonzero, this key is authorized to add and /// delete RRs even if there are other RRs with the same owner name /// and class that are authenticated by a SIG signed with a /// different dynamic update KEY. If zero, the key can only /// authorize updates where any existing RRs of the same owner and /// class are authenticated by a SIG using the same key. This bit /// is meaningful only for type A dynamic zones and is ignored in /// type B dynamic zones. /// /// Keeping this bit zero on multiple KEY RRs with the same or /// nested wild card owner names permits multiple entities to exist /// that can create and delete names but can not effect RRs with /// different owner names from any they created. In effect, this /// creates two levels of dynamic update key, strong and weak, where /// weak keys are limited in interfering with each other but a /// strong key can interfere with any weak keys or other strong /// keys. /// /// Bit 2, unique name update - If nonzero, this key is authorized to add /// and update RRs for only a single owner name. If there already /// exist RRs with one or more names signed by this key, they may be /// updated but no new name created until the number of existing /// names is reduced to zero. This bit is meaningful only for mode /// A dynamic zones and is ignored in mode B dynamic zones. This bit /// is meaningful only if the owner name is a wildcard. (Any /// dynamic update KEY with a non-wildcard name is, in effect, a /// unique name update key.) /// /// This bit can be used to restrict a KEY from flooding a zone with /// new names. In conjunction with a local administratively imposed /// limit on the number of dynamic RRs with a particular name, it /// can completely restrict a KEY from flooding a zone with RRs. /// /// Bit 3, general update - The general update signatory field bit has no /// special meaning. If the other three bits are all zero, it must /// be one so that the field is non-zero to designate that the key /// is an update key. The meaning of all values of the signatory /// field with the general bit and one or more other signatory field /// bits on is reserved. /// /// All the signatory bit update authorizations described above only /// apply if the update is within the name and class scope as per /// sections 3.1.1 and 3.1.2. /// ``` /// /// [RFC 3007](https://tools.ietf.org/html/rfc3007#section-1.5), Secure Dynamic Update, November 2000 /// /// ```text /// [RFC2535, section 3.1.2] defines the signatory field of a key as the /// final 4 bits of the flags field, but does not define its value. This /// proposal leaves this field undefined. Updating [RFC2535], this field /// SHOULD be set to 0 in KEY records, and MUST be ignored. /// /// ``` #[deprecated = "Deprecated by RFC3007"] #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)] pub struct UpdateScope { /// this key is authorized to attach, /// detach, and move zones by creating and deleting NS, glue A, and /// zone KEY RR(s) pub zone: bool, /// this key is authorized to add and /// delete RRs even if there are other RRs with the same owner name /// and class that are authenticated by a SIG signed with a /// different dynamic update KEY pub strong: bool, /// this key is authorized to add and update RRs for only a single owner name pub unique: bool, /// The general update signatory field bit has no special meaning, (true if the others are false) pub general: bool, } impl From for UpdateScope { fn from(flags: u16) -> Self { // we only care about the final four bits, zero out the rest Self { // Bit 0, zone control - If nonzero, this key is authorized to attach, zone: flags & 0b0000_0000_0000_1000 != 0, // Bit 1, strong update - If nonzero, this key is authorized to add and strong: flags & 0b0000_0000_0000_0100 != 0, // Bit 2, unique name update - If nonzero, this key is authorized to add unique: flags & 0b0000_0000_0000_0010 != 0, // Bit 3, general update - The general update signatory field bit has no general: flags & 0b0000_0000_0000_0001 != 0, } } } impl From for u16 { fn from(update_scope: UpdateScope) -> Self { let mut flags = 0_u16; if update_scope.zone { flags |= 0b0000_0000_0000_1000; } if update_scope.strong { flags |= 0b0000_0000_0000_0100; } if update_scope.unique { flags |= 0b0000_0000_0000_0010; } if update_scope.general { flags |= 0b0000_0000_0000_0001; } flags } } #[test] fn test_update_scope() { assert_eq!( UpdateScope::default(), UpdateScope::from(u16::from(UpdateScope::default())) ); let update_scope = UpdateScope { zone: true, strong: true, unique: true, general: true, }; assert_eq!(update_scope, UpdateScope::from(u16::from(update_scope))); let update_scope = UpdateScope { zone: true, strong: false, unique: true, general: false, }; assert_eq!(update_scope, UpdateScope::from(u16::from(update_scope))); let update_scope = UpdateScope { zone: false, strong: true, unique: false, general: true, }; assert_eq!(update_scope, UpdateScope::from(u16::from(update_scope))); let update_scope = UpdateScope { zone: false, strong: true, unique: true, general: false, }; assert_eq!(update_scope, UpdateScope::from(u16::from(update_scope))); let update_scope = UpdateScope { zone: true, strong: false, unique: false, general: true, }; assert_eq!(update_scope, UpdateScope::from(u16::from(update_scope))); } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-3.1.3), Domain Name System Security Extensions, March 1999 /// /// ```text /// 3.1.3 The Protocol Octet /// /// It is anticipated that keys stored in DNS will be used in conjunction /// with a variety of Internet protocols. It is intended that the /// protocol octet and possibly some of the currently unused (must be /// zero) bits in the KEY RR flags as specified in the future will be /// used to indicate a key's validity for different protocols. /// /// The following values of the Protocol Octet are reserved as indicated: /// /// VALUE Protocol /// /// 0 -reserved /// 1 TLS /// 2 email /// 3 dnssec /// 4 IPSEC /// 5-254 - available for assignment by IANA /// 255 All /// /// In more detail: /// 1 is reserved for use in connection with TLS. /// 2 is reserved for use in connection with email. /// 3 is used for DNS security. The protocol field SHOULD be set to /// this value for zone keys and other keys used in DNS security. /// Implementations that can determine that a key is a DNS /// security key by the fact that flags label it a zone key or the /// signatory flag field is non-zero are NOT REQUIRED to check the /// protocol field. /// 4 is reserved to refer to the Oakley/IPSEC [RFC 2401] protocol /// and indicates that this key is valid for use in conjunction /// with that security standard. This key could be used in /// connection with secured communication on behalf of an end /// entity or user whose name is the owner name of the KEY RR if /// the entity or user flag bits are set. The presence of a KEY /// resource with this protocol value is an assertion that the /// host speaks Oakley/IPSEC. /// 255 indicates that the key can be used in connection with any /// protocol for which KEY RR protocol octet values have been /// defined. The use of this value is discouraged and the use of /// different keys for different protocols is encouraged. /// ``` /// /// [RFC3445](https://tools.ietf.org/html/rfc3445#section-4), Limiting the KEY Resource Record (RR), December 2002 /// /// ```text /// All Protocol Octet values except DNSSEC (3) are eliminated /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum Protocol { /// Not in use #[deprecated = "Deprecated by RFC3445"] Reserved, /// Reserved for use with TLS #[deprecated = "Deprecated by RFC3445"] TLS, /// Reserved for use with email #[deprecated = "Deprecated by RFC3445"] Email, /// Reserved for use with DNSSEC (Hickory DNS only supports DNSKEY with DNSSEC) DNSSEC, /// Reserved to refer to the Oakley/IPSEC #[deprecated = "Deprecated by RFC3445"] IPSec, /// Undefined #[deprecated = "Deprecated by RFC3445"] Other(u8), /// the key can be used in connection with any protocol #[deprecated = "Deprecated by RFC3445"] All, } impl Default for Protocol { fn default() -> Self { Self::DNSSEC } } impl From for Protocol { fn from(field: u8) -> Self { match field { 0 => Self::Reserved, 1 => Self::TLS, 2 => Self::Email, 3 => Self::DNSSEC, 4 => Self::IPSec, 255 => Self::All, _ => Self::Other(field), } } } impl From for u8 { fn from(protocol: Protocol) -> Self { match protocol { Protocol::Reserved => 0, Protocol::TLS => 1, Protocol::Email => 2, Protocol::DNSSEC => 3, Protocol::IPSec => 4, Protocol::All => 255, Protocol::Other(field) => field, } } } impl KEY { /// Construct a new KEY RData /// /// # Arguments /// /// * `key_trust` - declare the security level of this key /// * `key_usage` - what type of thing is this key associated to /// * `revoke` - this key has been revoked /// * `algorithm` - specifies the algorithm which this Key uses to sign records /// * `public_key` - the public key material, in native endian, the emitter will perform any necessary conversion /// /// # Return /// /// A new KEY RData for use in a Resource Record pub fn new( key_trust: KeyTrust, key_usage: KeyUsage, signatory: UpdateScope, protocol: Protocol, algorithm: Algorithm, public_key: Vec, ) -> Self { Self { key_trust, key_usage, signatory, protocol, algorithm, public_key, } } /// Returns the trust level of the key pub fn key_trust(&self) -> KeyTrust { self.key_trust } /// Returns the entity type using this key pub fn key_usage(&self) -> KeyUsage { self.key_usage } /// Returns the signatory information of the KEY pub fn signatory(&self) -> UpdateScope { self.signatory } /// Returns true if the key_trust is DoNotTrust pub fn revoke(&self) -> bool { self.key_trust == KeyTrust::DoNotTrust } /// Returns the protocol which this key can be used with pub fn protocol(&self) -> Protocol { self.protocol } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.3) /// /// ```text /// 2.1.3. The Algorithm Field /// /// The Algorithm field identifies the public key's cryptographic /// algorithm and determines the format of the Public Key field. A list /// of DNSSEC algorithm types can be found in Appendix A.1 /// ``` pub fn algorithm(&self) -> Algorithm { self.algorithm } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2.1.4) /// /// ```text /// 2.1.4. The Public Key Field /// /// The Public Key Field holds the public key material. The format /// depends on the algorithm of the key being stored and is described in /// separate documents. /// ``` pub fn public_key(&self) -> &[u8] { &self.public_key } /// Output the encoded form of the flags pub fn flags(&self) -> u16 { let mut flags: u16 = 0; flags |= u16::from(self.key_trust); flags |= u16::from(self.key_usage); flags |= u16::from(self.signatory); flags } // /// Creates a message digest for this KEY record. // /// // /// ```text // /// 5.1.4. The Digest Field // /// // /// The DS record refers to a KEY RR by including a digest of that // /// KEY RR. // /// // /// The digest is calculated by concatenating the canonical form of the // /// fully qualified owner name of the KEY RR with the KEY RDATA, // /// and then applying the digest algorithm. // /// // /// digest = digest_algorithm( KEY owner name | KEY RDATA); // /// // /// "|" denotes concatenation // /// // /// KEY RDATA = Flags | Protocol | Algorithm | Public Key. // /// // /// The size of the digest may vary depending on the digest algorithm and // /// KEY RR size. As of the time of this writing, the only defined // /// digest algorithm is SHA-1, which produces a 20 octet digest. // /// ``` // /// // /// # Arguments // /// // /// * `name` - the label of of the KEY record. // /// * `digest_type` - the `DigestType` with which to create the message digest. // pub fn to_digest(&self, name: &Name, digest_type: DigestType) -> ProtoResult> { // let mut buf: Vec = Vec::new(); // { // let mut encoder: BinEncoder = BinEncoder::new(&mut buf); // encoder.set_canonical_names(true); // if let Err(e) = name.emit(&mut encoder) // .and_then(|_| emit(&mut encoder, self)) { // warn!("error serializing KEY: {}", e); // return Err(format!("error serializing KEY: {}", e).into()); // } // } // digest_type.hash(&buf).map_err(|e| e.into()) // } } impl From for RData { fn from(key: KEY) -> Self { Self::DNSSEC(super::DNSSECRData::KEY(key)) } } impl BinEncodable for KEY { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.emit_u16(self.flags())?; encoder.emit(u8::from(self.protocol))?; self.algorithm().emit(encoder)?; encoder.emit_vec(self.public_key())?; Ok(()) } } impl<'r> RecordDataDecodable<'r> for KEY { fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // | A/C | Z | XT| Z | Z | NAMTYP| Z | Z | Z | Z | SIG | // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ let flags: u16 = decoder .read_u16()? .verify_unwrap(|flags| { // Bits 2 is reserved and must be zero. // Bits 4-5 are reserved and must be zero. // Bits 8-11 are reserved and must be zero. flags & 0b0010_1100_1111_0000 == 0 }) .map_err(|_| ProtoError::from("flag 2, 4-5, and 8-11 are reserved, must be zero"))?; let key_trust = KeyTrust::from(flags); let extended_flags: bool = flags & 0b0001_0000_0000_0000 != 0; let key_usage = KeyUsage::from(flags); let signatory = UpdateScope::from(flags); if extended_flags { // TODO: add an optional field to return the raw u16? return Err("extended flags currently not supported".into()); } // TODO: protocol my be infallible let protocol = Protocol::from(decoder.read_u8()?.unverified(/*Protocol is verified as safe*/)); let algorithm: Algorithm = Algorithm::read(decoder)?; // the public key is the left-over bytes minus 4 for the first fields // TODO: decode the key here? let key_len = length .map(|u| u as usize) .checked_sub(4) .map_err(|_| ProtoError::from("invalid rdata length in KEY"))? .unverified(/*used only as length safely*/); let public_key: Vec = decoder.read_vec(key_len)?.unverified(/*the byte array will fail in usage if invalid*/); Ok(Self::new( key_trust, key_usage, signatory, protocol, algorithm, public_key, )) } } impl RecordData for KEY { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::KEY(csync)) => Ok(csync), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::KEY(csync)) => Some(csync), _ => None, } } fn record_type(&self) -> RecordType { RecordType::KEY } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::KEY(self)) } } /// Note that KEY is a deprecated type in DNS /// /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-7.1), Domain Name System Security Extensions, March 1999 /// /// ```text /// 7.1 Presentation of KEY RRs /// /// KEY RRs may appear as single logical lines in a zone data master file /// [RFC 1033]. /// /// The flag field is represented as an unsigned integer or a sequence of /// mnemonics as follows separated by instances of the vertical bar ("|") /// character: /// /// BIT Mnemonic Explanation /// 0-1 key type /// NOCONF =1 confidentiality use prohibited /// NOAUTH =2 authentication use prohibited /// NOKEY =3 no key present /// 2 FLAG2 - reserved /// 3 EXTEND flags extension /// 4 FLAG4 - reserved /// 5 FLAG5 - reserved /// 6-7 name type /// USER =0 (default, may be omitted) /// ZONE =1 /// HOST =2 (host or other end entity) /// NTYP3 - reserved /// 8 FLAG8 - reserved /// 9 FLAG9 - reserved /// 10 FLAG10 - reserved /// 11 FLAG11 - reserved /// 12-15 signatory field, values 0 to 15 /// can be represented by SIG0, SIG1, ... SIG15 /// /// No flag mnemonic need be present if the bit or field it represents is /// zero. /// /// The protocol octet can be represented as either an unsigned integer /// or symbolically. The following initial symbols are defined: /// /// 000 NONE /// 001 TLS /// 002 EMAIL /// 003 DNSSEC /// 004 IPSEC /// 255 ALL /// /// Note that if the type flags field has the NOKEY value, nothing /// appears after the algorithm octet. /// /// The remaining public key portion is represented in base 64 (see /// Appendix A) and may be divided up into any number of white space /// separated substrings, down to single base 64 digits, which are /// concatenated to obtain the full signature. These substrings can span /// lines using the standard parenthesis. /// /// Note that the public key may have internal sub-fields but these do /// not appear in the master file representation. For example, with /// algorithm 1 there is a public exponent size, then a public exponent, /// and then a modulus. With algorithm 254, there will be an OID size, /// an OID, and algorithm dependent information. But in both cases only a /// single logical base 64 string will appear in the master file. /// ``` impl fmt::Display for KEY { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, "{flags} {proto} {alg} {key}", flags = self.flags(), proto = u8::from(self.protocol), alg = self.algorithm, key = data_encoding::BASE64.encode(&self.public_key) ) } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; #[test] fn test() { let rdata = KEY::new( KeyTrust::default(), KeyUsage::default(), UpdateScope::default(), Protocol::default(), Algorithm::RSASHA256, vec![0, 1, 2, 3, 4, 5, 6, 7], ); let mut bytes = Vec::new(); let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); assert!(rdata.emit(&mut encoder).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {bytes:?}"); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let restrict = Restrict::new(bytes.len() as u16); let read_rdata = KEY::read_data(&mut decoder, restrict).expect("Decoding error"); assert_eq!(rdata, read_rdata); // #[cfg(any(feature = "openssl", feature = "ring"))] // assert!(rdata // .to_digest(&Name::parse("www.example.com.", None).unwrap(), // DigestType::SHA256) // .is_ok()); } } hickory-proto-0.24.0/src/rr/dnssec/rdata/mod.rs000064400000000000000000001003731046102023000174350ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! All record data structures and related serialization methods use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; // TODO: these should each be it's own struct, it would make parsing and decoding a little cleaner // and also a little more ergonomic when accessing. // each of these module's has the parser for that rdata embedded, to keep the file sizes down... pub mod cdnskey; pub mod cds; pub mod dnskey; pub mod ds; #[allow(deprecated)] pub mod key; pub mod nsec; pub mod nsec3; pub mod nsec3param; pub mod rrsig; pub mod sig; pub mod tsig; use enum_as_inner::EnumAsInner; use tracing::trace; use crate::{ error::*, rr::{rdata::NULL, RData, RecordDataDecodable, RecordType}, serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder, Restrict}, }; pub use self::cdnskey::CDNSKEY; pub use self::cds::CDS; pub use self::dnskey::DNSKEY; pub use self::ds::DS; pub use self::key::KEY; pub use self::nsec::NSEC; pub use self::nsec3::NSEC3; pub use self::nsec3param::NSEC3PARAM; pub use self::rrsig::RRSIG; pub use self::sig::SIG; pub use self::tsig::TSIG; /// The type of the resource record, for DNSSEC-specific records. #[deprecated(note = "All RecordType definitions have been moved into RecordType")] pub type DNSSECRecordType = RecordType; /// Record data enum variants for DNSSEC-specific records. #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, EnumAsInner, PartialEq, Clone, Eq)] #[non_exhaustive] pub enum DNSSECRData { /// ```text /// RFC 7344 Delegation Trust Maintenance September 2014 /// /// 3.2. CDNSKEY Resource Record Format /// /// The wire and presentation format of the CDNSKEY ("Child DNSKEY") /// resource record is identical to the DNSKEY record. IANA has /// allocated RR code 60 for the CDNSKEY resource record via Expert /// Review. The CDNSKEY RR uses the same registries as DNSKEY for its /// fields. /// /// No special processing is performed by authoritative servers or by /// resolvers, when serving or resolving. For all practical purposes, /// CDNSKEY is a regular RR type. /// ``` CDNSKEY(CDNSKEY), /// ```text /// RFC 7344 Delegation Trust Maintenance September 2014 /// /// 3.1. CDS Resource Record Format /// The wire and presentation format of the Child DS (CDS) resource /// record is identical to the DS record [RFC4034]. IANA has allocated /// RR code 59 for the CDS resource record via Expert Review /// [DNS-TRANSPORT]. The CDS RR uses the same registries as DS for its /// fields. /// /// No special processing is performed by authoritative servers or by /// resolvers, when serving or resolving. For all practical purposes, /// CDS is a regular RR type. /// ``` CDS(CDS), /// ```text /// RFC 4034 DNSSEC Resource Records March 2005 /// /// 2.1. DNSKEY RDATA Wire Format /// /// The RDATA for a DNSKEY RR consists of a 2 octet Flags Field, a 1 /// octet Protocol Field, a 1 octet Algorithm Field, and the Public Key /// Field. /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Flags | Protocol | Algorithm | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / / /// / Public Key / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// 2.1.1. The Flags Field /// /// Bit 7 of the Flags field is the Zone Key flag. If bit 7 has value 1, /// then the DNSKEY record holds a DNS zone key, and the DNSKEY RR's /// owner name MUST be the name of a zone. If bit 7 has value 0, then /// the DNSKEY record holds some other type of DNS public key and MUST /// NOT be used to verify RRSIGs that cover RRsets. /// /// Bit 15 of the Flags field is the Secure Entry Point flag, described /// in [RFC3757]. If bit 15 has value 1, then the DNSKEY record holds a /// key intended for use as a secure entry point. This flag is only /// intended to be a hint to zone signing or debugging software as to the /// intended use of this DNSKEY record; validators MUST NOT alter their /// behavior during the signature validation process in any way based on /// the setting of this bit. This also means that a DNSKEY RR with the /// SEP bit set would also need the Zone Key flag set in order to be able /// to generate signatures legally. A DNSKEY RR with the SEP set and the /// Zone Key flag not set MUST NOT be used to verify RRSIGs that cover /// RRsets. /// /// Bits 0-6 and 8-14 are reserved: these bits MUST have value 0 upon /// creation of the DNSKEY RR and MUST be ignored upon receipt. /// /// RFC 5011 Trust Anchor Update September 2007 /// /// 7. IANA Considerations /// /// The IANA has assigned a bit in the DNSKEY flags field (see Section 7 /// of [RFC4034]) for the REVOKE bit (8). /// ``` DNSKEY(DNSKEY), /// ```text /// 5.1. DS RDATA Wire Format /// /// The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet /// Algorithm field, a 1 octet Digest Type field, and a Digest field. /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Key Tag | Algorithm | Digest Type | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / / /// / Digest / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// 5.1.1. The Key Tag Field /// /// The Key Tag field lists the key tag of the DNSKEY RR referred to by /// the DS record, in network byte order. /// /// The Key Tag used by the DS RR is identical to the Key Tag used by /// RRSIG RRs. Appendix B describes how to compute a Key Tag. /// /// 5.1.2. The Algorithm Field /// /// The Algorithm field lists the algorithm number of the DNSKEY RR /// referred to by the DS record. /// /// The algorithm number used by the DS RR is identical to the algorithm /// number used by RRSIG and DNSKEY RRs. Appendix A.1 lists the /// algorithm number types. /// /// 5.1.3. The Digest Type Field /// /// The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY /// RR. The Digest Type field identifies the algorithm used to construct /// the digest. Appendix A.2 lists the possible digest algorithm types. /// /// 5.1.4. The Digest Field /// /// The DS record refers to a DNSKEY RR by including a digest of that /// DNSKEY RR. /// /// The digest is calculated by concatenating the canonical form of the /// fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA, /// and then applying the digest algorithm. /// /// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); /// /// "|" denotes concatenation /// /// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. /// /// The size of the digest may vary depending on the digest algorithm and /// DNSKEY RR size. As of the time of this writing, the only defined /// digest algorithm is SHA-1, which produces a 20 octet digest. /// ``` DS(DS), /// ```text /// RFC 2535 DNS Security Extensions March 1999 /// /// 3.1 KEY RDATA format /// /// The RDATA for a KEY RR consists of flags, a protocol octet, the /// algorithm number octet, and the public key itself. The format is as /// follows: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | flags | protocol | algorithm | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | / /// / public key / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| /// /// The KEY RR is not intended for storage of certificates and a separate /// certificate RR has been developed for that purpose, defined in [RFC /// 2538]. /// /// The meaning of the KEY RR owner name, flags, and protocol octet are /// described in Sections 3.1.1 through 3.1.5 below. The flags and /// algorithm must be examined before any data following the algorithm /// octet as they control the existence and format of any following data. /// The algorithm and public key fields are described in Section 3.2. /// The format of the public key is algorithm dependent. /// /// KEY RRs do not specify their validity period but their authenticating /// SIG RR(s) do as described in Section 4 below. /// ``` KEY(KEY), /// ```text /// RFC 4034 DNSSEC Resource Records March 2005 /// /// 4.1. NSEC RDATA Wire Format /// /// The RDATA of the NSEC RR is as shown below: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Next Domain Name / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Type Bit Maps / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// ``` NSEC(NSEC), /// ```text /// RFC 5155 NSEC3 March 2008 /// /// 3.2. NSEC3 RDATA Wire Format /// /// The RDATA of the NSEC3 RR is as shown below: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Hash Alg. | Flags | Iterations | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Salt Length | Salt / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Hash Length | Next Hashed Owner Name / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Type Bit Maps / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// Hash Algorithm is a single octet. /// /// Flags field is a single octet, the Opt-Out flag is the least /// significant bit, as shown below: /// /// 0 1 2 3 4 5 6 7 /// +-+-+-+-+-+-+-+-+ /// | |O| /// +-+-+-+-+-+-+-+-+ /// /// Iterations is represented as a 16-bit unsigned integer, with the most /// significant bit first. /// /// Salt Length is represented as an unsigned octet. Salt Length /// represents the length of the Salt field in octets. If the value is /// zero, the following Salt field is omitted. /// /// Salt, if present, is encoded as a sequence of binary octets. The /// length of this field is determined by the preceding Salt Length /// field. /// /// Hash Length is represented as an unsigned octet. Hash Length /// represents the length of the Next Hashed Owner Name field in octets. /// /// The next hashed owner name is not base32 encoded, unlike the owner /// name of the NSEC3 RR. It is the unmodified binary hash value. It /// does not include the name of the containing zone. The length of this /// field is determined by the preceding Hash Length field. /// /// 3.2.1. Type Bit Maps Encoding /// /// The encoding of the Type Bit Maps field is the same as that used by /// the NSEC RR, described in [RFC4034]. It is explained and clarified /// here for clarity. /// /// The RR type space is split into 256 window blocks, each representing /// the low-order 8 bits of the 16-bit RR type space. Each block that /// has at least one active RR type is encoded using a single octet /// window number (from 0 to 255), a single octet bitmap length (from 1 /// to 32) indicating the number of octets used for the bitmap of the /// window block, and up to 32 octets (256 bits) of bitmap. /// /// Blocks are present in the NSEC3 RR RDATA in increasing numerical /// order. /// /// Type Bit Maps Field = ( Window Block # | Bitmap Length | Bitmap )+ /// /// where "|" denotes concatenation. /// /// Each bitmap encodes the low-order 8 bits of RR types within the /// window block, in network bit order. The first bit is bit 0. For /// window block 0, bit 1 corresponds to RR type 1 (A), bit 2 corresponds /// to RR type 2 (NS), and so forth. For window block 1, bit 1 /// corresponds to RR type 257, bit 2 to RR type 258. If a bit is set to /// 1, it indicates that an RRSet of that type is present for the /// original owner name of the NSEC3 RR. If a bit is set to 0, it /// indicates that no RRSet of that type is present for the original /// owner name of the NSEC3 RR. /// /// Since bit 0 in window block 0 refers to the non-existing RR type 0, /// it MUST be set to 0. After verification, the validator MUST ignore /// the value of bit 0 in window block 0. /// /// Bits representing Meta-TYPEs or QTYPEs as specified in Section 3.1 of /// [RFC2929] or within the range reserved for assignment only to QTYPEs /// and Meta-TYPEs MUST be set to 0, since they do not appear in zone /// data. If encountered, they must be ignored upon reading. /// /// Blocks with no types present MUST NOT be included. Trailing zero /// octets in the bitmap MUST be omitted. The length of the bitmap of /// each block is determined by the type code with the largest numerical /// value, within that block, among the set of RR types present at the /// original owner name of the NSEC3 RR. Trailing octets not specified /// MUST be interpreted as zero octets. /// ``` NSEC3(NSEC3), /// ```text /// RFC 5155 NSEC3 March 2008 /// /// 4.2. NSEC3PARAM RDATA Wire Format /// /// The RDATA of the NSEC3PARAM RR is as shown below: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Hash Alg. | Flags | Iterations | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Salt Length | Salt / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// Hash Algorithm is a single octet. /// /// Flags field is a single octet. /// /// Iterations is represented as a 16-bit unsigned integer, with the most /// significant bit first. /// /// Salt Length is represented as an unsigned octet. Salt Length /// represents the length of the following Salt field in octets. If the /// value is zero, the Salt field is omitted. /// /// Salt, if present, is encoded as a sequence of binary octets. The /// length of this field is determined by the preceding Salt Length /// field. /// ``` NSEC3PARAM(NSEC3PARAM), /// ```text /// RFC 2535 & 2931 DNS Security Extensions March 1999 /// RFC 4034 DNSSEC Resource Records March 2005 /// /// 3.1. RRSIG RDATA Wire Format /// /// The RDATA for an RRSIG RR consists of a 2 octet Type Covered field, a /// 1 octet Algorithm field, a 1 octet Labels field, a 4 octet Original /// TTL field, a 4 octet Signature Expiration field, a 4 octet Signature /// Inception field, a 2 octet Key tag, the Signer's Name field, and the /// Signature field. /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Type Covered | Algorithm | Labels | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Original TTL | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Signature Expiration | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Signature Inception | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Key Tag | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Signer's Name / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / / /// / Signature / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// ``` RRSIG(RRSIG), /// ```text /// RFC 2535 & 2931 DNS Security Extensions March 1999 /// RFC 4034 DNSSEC Resource Records March 2005 /// /// 3.1. RRSIG RDATA Wire Format /// /// The RDATA for an RRSIG RR consists of a 2 octet Type Covered field, a /// 1 octet Algorithm field, a 1 octet Labels field, a 4 octet Original /// TTL field, a 4 octet Signature Expiration field, a 4 octet Signature /// Inception field, a 2 octet Key tag, the Signer's Name field, and the /// Signature field. /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Type Covered | Algorithm | Labels | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Original TTL | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Signature Expiration | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Signature Inception | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Key Tag | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Signer's Name / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / / /// / Signature / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// ``` SIG(SIG), /// [RFC 8945, Secret Key Transaction Authentication for DNS](https://tools.ietf.org/html/rfc8945#section-4.2) /// /// ```text /// 4.2. TSIG Record Format /// /// The fields of the TSIG RR are described below. All multi-octet /// integers in the record are sent in network byte order (see /// Section 2.3.2 of [RFC1035]). /// /// NAME: The name of the key used, in domain name syntax. The name /// should reflect the names of the hosts and uniquely identify the /// key among a set of keys these two hosts may share at any given /// time. For example, if hosts A.site.example and B.example.net /// share a key, possibilities for the key name include /// .A.site.example, .B.example.net, and /// .A.site.example.B.example.net. It should be possible for more /// than one key to be in simultaneous use among a set of interacting /// hosts. This allows for periodic key rotation as per best /// operational practices, as well as algorithm agility as indicated /// by [RFC7696]. /// /// The name may be used as a local index to the key involved, but it /// is recommended that it be globally unique. Where a key is just /// shared between two hosts, its name actually need only be /// meaningful to them, but it is recommended that the key name be /// mnemonic and incorporate the names of participating agents or /// resources as suggested above. /// /// TYPE: This MUST be TSIG (250: Transaction SIGnature). /// /// CLASS: This MUST be ANY. /// /// TTL: This MUST be 0. /// /// RDLENGTH: (variable) /// /// RDATA: The RDATA for a TSIG RR consists of a number of fields, /// described below: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Algorithm Name / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// | Time Signed +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | Fudge | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | MAC Size | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ MAC / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Original ID | Error | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Other Len | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Other Data / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// The contents of the RDATA fields are: /// /// Algorithm Name: /// an octet sequence identifying the TSIG algorithm in the domain /// name syntax. (Allowed names are listed in Table 3.) The name is /// stored in the DNS name wire format as described in [RFC1034]. As /// per [RFC3597], this name MUST NOT be compressed. /// /// Time Signed: /// an unsigned 48-bit integer containing the time the message was /// signed as seconds since 00:00 on 1970-01-01 UTC, ignoring leap /// seconds. /// /// Fudge: /// an unsigned 16-bit integer specifying the allowed time difference /// in seconds permitted in the Time Signed field. /// /// MAC Size: /// an unsigned 16-bit integer giving the length of the MAC field in /// octets. Truncation is indicated by a MAC Size less than the size /// of the keyed hash produced by the algorithm specified by the /// Algorithm Name. /// /// MAC: /// a sequence of octets whose contents are defined by the TSIG /// algorithm used, possibly truncated as specified by the MAC Size. /// The length of this field is given by the MAC Size. Calculation of /// the MAC is detailed in Section 4.3. /// /// Original ID: /// an unsigned 16-bit integer holding the message ID of the original /// request message. For a TSIG RR on a request, it is set equal to /// the DNS message ID. In a TSIG attached to a response -- or in /// cases such as the forwarding of a dynamic update request -- the /// field contains the ID of the original DNS request. /// /// Error: /// in responses, an unsigned 16-bit integer containing the extended /// RCODE covering TSIG processing. In requests, this MUST be zero. /// /// Other Len: /// an unsigned 16-bit integer specifying the length of the Other Data /// field in octets. /// /// Other Data: /// additional data relevant to the TSIG record. In responses, this /// will be empty (i.e., Other Len will be zero) unless the content of /// the Error field is BADTIME, in which case it will be a 48-bit /// unsigned integer containing the server's current time as the /// number of seconds since 00:00 on 1970-01-01 UTC, ignoring leap /// seconds (see Section 5.2.3). This document assigns no meaning to /// its contents in requests. /// ``` TSIG(TSIG), /// Unknown or unsupported DNSSEC record data Unknown { /// RecordType code code: u16, /// RData associated to the record rdata: NULL, }, } impl DNSSECRData { pub(crate) fn read( decoder: &mut BinDecoder<'_>, record_type: RecordType, rdata_length: Restrict, ) -> ProtoResult { match record_type { RecordType::CDNSKEY => { trace!("reading CDNSKEY"); CDNSKEY::read_data(decoder, rdata_length).map(Self::CDNSKEY) } RecordType::CDS => { trace!("reading CDS"); CDS::read_data(decoder, rdata_length).map(Self::CDS) } RecordType::DNSKEY => { trace!("reading DNSKEY"); DNSKEY::read_data(decoder, rdata_length).map(Self::DNSKEY) } RecordType::DS => { trace!("reading DS"); DS::read_data(decoder, rdata_length).map(Self::DS) } RecordType::KEY => { trace!("reading KEY"); KEY::read_data(decoder, rdata_length).map(Self::KEY) } RecordType::NSEC => { trace!("reading NSEC"); NSEC::read_data(decoder, rdata_length).map(Self::NSEC) } RecordType::NSEC3 => { trace!("reading NSEC3"); NSEC3::read_data(decoder, rdata_length).map(Self::NSEC3) } RecordType::NSEC3PARAM => { trace!("reading NSEC3PARAM"); NSEC3PARAM::read(decoder).map(Self::NSEC3PARAM) } RecordType::RRSIG => { trace!("reading RRSIG"); RRSIG::read_data(decoder, rdata_length).map(Self::RRSIG) } RecordType::SIG => { trace!("reading SIG"); SIG::read_data(decoder, rdata_length).map(Self::SIG) } RecordType::TSIG => { trace!("reading TSIG"); TSIG::read_data(decoder, rdata_length).map(Self::TSIG) } r => { panic!("not a dnssec RecordType: {}", r); } } } pub(crate) fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { match *self { Self::CDNSKEY(ref cdnskey) => { encoder.with_canonical_names(|encoder| cdnskey.emit(encoder)) } Self::CDS(ref cds) => encoder.with_canonical_names(|encoder| cds.emit(encoder)), Self::DS(ref ds) => encoder.with_canonical_names(|encoder| ds.emit(encoder)), Self::KEY(ref key) => encoder.with_canonical_names(|encoder| key.emit(encoder)), Self::DNSKEY(ref dnskey) => { encoder.with_canonical_names(|encoder| dnskey.emit(encoder)) } Self::NSEC(ref nsec) => encoder.with_canonical_names(|encoder| nsec.emit(encoder)), Self::NSEC3(ref nsec3) => encoder.with_canonical_names(|encoder| nsec3.emit(encoder)), Self::NSEC3PARAM(ref nsec3param) => { encoder.with_canonical_names(|encoder| nsec3param.emit(encoder)) } Self::RRSIG(ref rrsig) => encoder.with_canonical_names(|encoder| rrsig.emit(encoder)), Self::SIG(ref sig) => encoder.with_canonical_names(|encoder| sig.emit(encoder)), Self::TSIG(ref tsig) => tsig.emit(encoder), Self::Unknown { ref rdata, .. } => { encoder.with_canonical_names(|encoder| rdata.emit(encoder)) } } } pub(crate) fn to_record_type(&self) -> RecordType { match *self { Self::CDNSKEY(..) => RecordType::CDNSKEY, Self::CDS(..) => RecordType::CDS, Self::DS(..) => RecordType::DS, Self::KEY(..) => RecordType::KEY, Self::DNSKEY(..) => RecordType::DNSKEY, Self::NSEC(..) => RecordType::NSEC, Self::NSEC3(..) => RecordType::NSEC3, Self::NSEC3PARAM(..) => RecordType::NSEC3PARAM, Self::SIG(..) => RecordType::SIG, Self::RRSIG(..) => RecordType::RRSIG, Self::TSIG(..) => RecordType::TSIG, Self::Unknown { code, .. } => RecordType::Unknown(code), } } } impl fmt::Display for DNSSECRData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fn w(f: &mut fmt::Formatter<'_>, rdata: D) -> Result<(), fmt::Error> { write!(f, "{rdata}") } match self { Self::CDNSKEY(key) => w(f, key), Self::CDS(ds) => w(f, ds), Self::DS(ds) => w(f, ds), Self::KEY(key) => w(f, key), Self::DNSKEY(key) => w(f, key), Self::NSEC(nsec) => w(f, nsec), Self::NSEC3(nsec3) => w(f, nsec3), Self::NSEC3PARAM(nsec3param) => w(f, nsec3param), Self::SIG(sig) => w(f, sig), Self::RRSIG(rrsig) => w(f, rrsig), Self::TSIG(ref tsig) => w(f, tsig), Self::Unknown { rdata, .. } => w(f, rdata), } } } impl From for RData { fn from(rdata: DNSSECRData) -> Self { Self::DNSSEC(rdata) } } hickory-proto-0.24.0/src/rr/dnssec/rdata/nsec.rs000064400000000000000000000226371046102023000176140ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! NSEC record types use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::error::*; use crate::rr::type_bit_map::{decode_type_bit_maps, encode_type_bit_maps}; use crate::rr::{Name, RData, RecordData, RecordDataDecodable, RecordType}; use crate::serialize::binary::*; use super::DNSSECRData; /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-4), DNSSEC Resource Records, March 2005 /// /// ```text /// 4.1. NSEC RDATA Wire Format /// /// The RDATA of the NSEC RR is as shown below: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Next Domain Name / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Type Bit Maps / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// 4.1.3. Inclusion of Wildcard Names in NSEC RDATA /// /// If a wildcard owner name appears in a zone, the wildcard label ("*") /// is treated as a literal symbol and is treated the same as any other /// owner name for the purposes of generating NSEC RRs. Wildcard owner /// names appear in the Next Domain Name field without any wildcard /// expansion. [RFC4035] describes the impact of wildcards on /// authenticated denial of existence. /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct NSEC { next_domain_name: Name, type_bit_maps: Vec, } impl NSEC { /// Constructs a new NSEC RData, warning this won't guarantee that the NSEC covers itself /// which it should at it's own name. /// /// # Arguments /// /// * `next_domain_name` - the name labels of the next ordered name in the zone /// * `type_bit_maps` - a bit map of the types that exist at this name /// /// # Returns /// /// An NSEC RData for use in a Resource Record pub fn new(next_domain_name: Name, type_bit_maps: Vec) -> Self { Self { next_domain_name, type_bit_maps, } } /// Constructs a new NSEC RData, this will add the NSEC itself as covered, generally /// correct for NSEC records generated at their own name /// /// # Arguments /// /// * `next_domain_name` - the name labels of the next ordered name in the zone /// * `type_bit_maps` - a bit map of the types that exist at this name /// /// # Returns /// /// An NSEC RData for use in a Resource Record pub fn new_cover_self(next_domain_name: Name, mut type_bit_maps: Vec) -> Self { type_bit_maps.push(RecordType::NSEC); Self::new(next_domain_name, type_bit_maps) } /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-4.1.1), DNSSEC Resource Records, March 2005 /// /// ```text /// 4.1.1. The Next Domain Name Field /// /// The Next Domain field contains the next owner name (in the canonical /// ordering of the zone) that has authoritative data or contains a /// delegation point NS RRset; see Section 6.1 for an explanation of /// canonical ordering. The value of the Next Domain Name field in the /// last NSEC record in the zone is the name of the zone apex (the owner /// name of the zone's SOA RR). This indicates that the owner name of /// the NSEC RR is the last name in the canonical ordering of the zone. /// /// A sender MUST NOT use DNS name compression on the Next Domain Name /// field when transmitting an NSEC RR. /// /// Owner names of RRsets for which the given zone is not authoritative /// (such as glue records) MUST NOT be listed in the Next Domain Name /// unless at least one authoritative RRset exists at the same owner /// name. /// ``` pub fn next_domain_name(&self) -> &Name { &self.next_domain_name } /// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-4.1.2) /// /// ```text /// 4.1.2. The Type Bit Maps Field /// /// The Type Bit Maps field identifies the RRset types that exist at the /// NSEC RR's owner name. /// /// A zone MUST NOT include an NSEC RR for any domain name that only /// holds glue records. /// ``` pub fn type_bit_maps(&self) -> &[RecordType] { &self.type_bit_maps } } impl BinEncodable for NSEC { /// [RFC 6840](https://tools.ietf.org/html/rfc6840#section-6) /// /// ```text /// 5.1. Errors in Canonical Form Type Code List /// /// When canonicalizing DNS names (for both ordering and signing), DNS /// names in the RDATA section of NSEC resource records are not converted /// to lowercase. DNS names in the RDATA section of RRSIG resource /// records are converted to lowercase. /// ``` fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.with_canonical_names(|encoder| { self.next_domain_name().emit(encoder)?; encode_type_bit_maps(encoder, self.type_bit_maps()) }) } } impl<'r> RecordDataDecodable<'r> for NSEC { fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { let start_idx = decoder.index(); let next_domain_name = Name::read(decoder)?; let bit_map_len = length .map(|u| u as usize) .checked_sub(decoder.index() - start_idx) .map_err(|_| ProtoError::from("invalid rdata length in NSEC"))?; let record_types = decode_type_bit_maps(decoder, bit_map_len)?; Ok(Self::new(next_domain_name, record_types)) } } impl RecordData for NSEC { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::NSEC(csync)) => Ok(csync), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::NSEC(csync)) => Some(csync), _ => None, } } fn record_type(&self) -> RecordType { RecordType::NSEC } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::NSEC(self)) } } /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-4.2), DNSSEC Resource Records, March 2005 /// /// ```text /// 4.2. The NSEC RR Presentation Format /// /// The presentation format of the RDATA portion is as follows: /// /// The Next Domain Name field is represented as a domain name. /// /// The Type Bit Maps field is represented as a sequence of RR type /// mnemonics. When the mnemonic is not known, the TYPE representation /// described in [RFC3597], Section 5, MUST be used. /// /// 4.3. NSEC RR Example /// /// The following NSEC RR identifies the RRsets associated with /// alfa.example.com. and identifies the next authoritative name after /// alfa.example.com. /// /// alfa.example.com. 86400 IN NSEC host.example.com. ( /// A MX RRSIG NSEC TYPE1234 ) /// /// The first four text fields specify the name, TTL, Class, and RR type /// (NSEC). The entry host.example.com. is the next authoritative name /// after alfa.example.com. in canonical order. The A, MX, RRSIG, NSEC, /// and TYPE1234 mnemonics indicate that there are A, MX, RRSIG, NSEC, /// and TYPE1234 RRsets associated with the name alfa.example.com. /// /// Assuming that the validator can authenticate this NSEC record, it /// could be used to prove that beta.example.com does not exist, or to /// prove that there is no AAAA record associated with alfa.example.com. /// Authenticated denial of existence is discussed in [RFC4035]. /// ``` impl fmt::Display for NSEC { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", self.next_domain_name)?; for ty in &self.type_bit_maps { write!(f, " {ty}")?; } Ok(()) } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; #[test] fn test() { use crate::rr::RecordType; use std::str::FromStr; let rdata = NSEC::new( Name::from_str("www.example.com").unwrap(), vec![ RecordType::A, RecordType::AAAA, RecordType::DS, RecordType::RRSIG, ], ); let mut bytes = Vec::new(); let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); assert!(rdata.emit(&mut encoder).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {bytes:?}"); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let restrict = Restrict::new(bytes.len() as u16); let read_rdata = NSEC::read_data(&mut decoder, restrict).expect("Decoding error"); assert_eq!(rdata, read_rdata); } } hickory-proto-0.24.0/src/rr/dnssec/rdata/nsec3.rs000064400000000000000000000421171046102023000176720ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! NSEC record types use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::*, rr::{ dnssec::Nsec3HashAlgorithm, type_bit_map::*, RData, RecordData, RecordDataDecodable, RecordType, }, serialize::binary::*, }; use super::DNSSECRData; /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3), NSEC3, March 2008 /// /// ```text /// 3. The NSEC3 Resource Record /// /// The NSEC3 Resource Record (RR) provides authenticated denial of /// existence for DNS Resource Record Sets. /// /// The NSEC3 RR lists RR types present at the original owner name of the /// NSEC3 RR. It includes the next hashed owner name in the hash order /// of the zone. The complete set of NSEC3 RRs in a zone indicates which /// RRSets exist for the original owner name of the RR and form a chain /// of hashed owner names in the zone. This information is used to /// provide authenticated denial of existence for DNS data. To provide /// protection against zone enumeration, the owner names used in the /// NSEC3 RR are cryptographic hashes of the original owner name /// prepended as a single label to the name of the zone. The NSEC3 RR /// indicates which hash function is used to construct the hash, which /// salt is used, and how many iterations of the hash function are /// performed over the original owner name. The hashing technique is /// described fully in Section 5. /// /// Hashed owner names of unsigned delegations may be excluded from the /// chain. An NSEC3 RR whose span covers the hash of an owner name or /// "next closer" name of an unsigned delegation is referred to as an /// Opt-Out NSEC3 RR and is indicated by the presence of a flag. /// /// The owner name for the NSEC3 RR is the base32 encoding of the hashed /// owner name prepended as a single label to the name of the zone. /// /// The type value for the NSEC3 RR is 50. /// /// The NSEC3 RR RDATA format is class independent and is described /// below. /// /// The class MUST be the same as the class of the original owner name. /// /// The NSEC3 RR SHOULD have the same TTL value as the SOA minimum TTL /// field. This is in the spirit of negative caching [RFC2308]. /// /// 3.2. NSEC3 RDATA Wire Format /// /// The RDATA of the NSEC3 RR is as shown below: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Hash Alg. | Flags | Iterations | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Salt Length | Salt / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Hash Length | Next Hashed Owner Name / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Type Bit Maps / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// Hash Algorithm is a single octet. /// /// Flags field is a single octet, the Opt-Out flag is the least /// significant bit, as shown below: /// /// 0 1 2 3 4 5 6 7 /// +-+-+-+-+-+-+-+-+ /// | |O| /// +-+-+-+-+-+-+-+-+ /// /// Iterations is represented as a 16-bit unsigned integer, with the most /// significant bit first. /// /// Salt Length is represented as an unsigned octet. Salt Length /// represents the length of the Salt field in octets. If the value is /// zero, the following Salt field is omitted. /// /// Salt, if present, is encoded as a sequence of binary octets. The /// length of this field is determined by the preceding Salt Length /// field. /// /// Hash Length is represented as an unsigned octet. Hash Length /// represents the length of the Next Hashed Owner Name field in octets. /// /// The next hashed owner name is not base32 encoded, unlike the owner /// name of the NSEC3 RR. It is the unmodified binary hash value. It /// does not include the name of the containing zone. The length of this /// field is determined by the preceding Hash Length field. /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct NSEC3 { hash_algorithm: Nsec3HashAlgorithm, opt_out: bool, iterations: u16, salt: Vec, next_hashed_owner_name: Vec, type_bit_maps: Vec, } impl NSEC3 { /// Constructs a new NSEC3 record pub fn new( hash_algorithm: Nsec3HashAlgorithm, opt_out: bool, iterations: u16, salt: Vec, next_hashed_owner_name: Vec, type_bit_maps: Vec, ) -> Self { Self { hash_algorithm, opt_out, iterations, salt, next_hashed_owner_name, type_bit_maps, } } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.1), NSEC3, March 2008 /// /// ```text /// 3.1.1. Hash Algorithm /// /// The Hash Algorithm field identifies the cryptographic hash algorithm /// used to construct the hash-value. /// /// The values for this field are defined in the NSEC3 hash algorithm /// registry defined in Section 11. /// ``` pub fn hash_algorithm(&self) -> Nsec3HashAlgorithm { self.hash_algorithm } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.2), NSEC3, March 2008 /// /// ```text /// 3.1.2. Flags /// /// The Flags field contains 8 one-bit flags that can be used to indicate /// different processing. All undefined flags must be zero. The only /// flag defined by this specification is the Opt-Out flag. /// /// 3.1.2.1. Opt-Out Flag /// /// If the Opt-Out flag is set, the NSEC3 record covers zero or more /// unsigned delegations. /// /// If the Opt-Out flag is clear, the NSEC3 record covers zero unsigned /// delegations. /// /// The Opt-Out Flag indicates whether this NSEC3 RR may cover unsigned /// delegations. It is the least significant bit in the Flags field. /// See Section 6 for details about the use of this flag. /// ``` pub fn opt_out(&self) -> bool { self.opt_out } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.3), NSEC3, March 2008 /// /// ```text /// 3.1.3. Iterations /// /// The Iterations field defines the number of additional times the hash /// function has been performed. More iterations result in greater /// resiliency of the hash value against dictionary attacks, but at a /// higher computational cost for both the server and resolver. See /// Section 5 for details of the use of this field, and Section 10.3 for /// limitations on the value. /// ``` pub fn iterations(&self) -> u16 { self.iterations } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.5), NSEC3, March 2008 /// /// ```text /// 3.1.5. Salt /// /// The Salt field is appended to the original owner name before hashing /// in order to defend against pre-calculated dictionary attacks. See /// Section 5 for details on how the salt is used. /// ``` pub fn salt(&self) -> &[u8] { &self.salt } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.7), NSEC3, March 2008 /// /// ```text /// 3.1.7. Next Hashed Owner Name /// /// The Next Hashed Owner Name field contains the next hashed owner name /// in hash order. This value is in binary format. Given the ordered /// set of all hashed owner names, the Next Hashed Owner Name field /// contains the hash of an owner name that immediately follows the owner /// name of the given NSEC3 RR. The value of the Next Hashed Owner Name /// field in the last NSEC3 RR in the zone is the same as the hashed /// owner name of the first NSEC3 RR in the zone in hash order. Note /// that, unlike the owner name of the NSEC3 RR, the value of this field /// does not contain the appended zone name. /// ``` pub fn next_hashed_owner_name(&self) -> &[u8] { &self.next_hashed_owner_name } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.1.8), NSEC3, March 2008 /// /// ```text /// 3.1.8. Type Bit Maps /// /// The Type Bit Maps field identifies the RRSet types that exist at the /// original owner name of the NSEC3 RR. /// ``` pub fn type_bit_maps(&self) -> &[RecordType] { &self.type_bit_maps } /// Flags for encoding pub fn flags(&self) -> u8 { let mut flags: u8 = 0; if self.opt_out { flags |= 0b0000_0001 }; flags } } impl BinEncodable for NSEC3 { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.emit(self.hash_algorithm().into())?; encoder.emit(self.flags())?; encoder.emit_u16(self.iterations())?; encoder.emit(self.salt().len() as u8)?; encoder.emit_vec(self.salt())?; encoder.emit(self.next_hashed_owner_name().len() as u8)?; encoder.emit_vec(self.next_hashed_owner_name())?; encode_type_bit_maps(encoder, self.type_bit_maps())?; Ok(()) } } impl<'r> RecordDataDecodable<'r> for NSEC3 { fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { let start_idx = decoder.index(); let hash_algorithm = Nsec3HashAlgorithm::from_u8( decoder.read_u8()?.unverified(/*Algorithm verified as safe*/), )?; let flags: u8 = decoder .read_u8()? .verify_unwrap(|flags| flags & 0b1111_1110 == 0) .map_err(|flags| ProtoError::from(ProtoErrorKind::UnrecognizedNsec3Flags(flags)))?; let opt_out: bool = flags & 0b0000_0001 == 0b0000_0001; let iterations: u16 = decoder.read_u16()?.unverified(/*valid as any u16*/); // read the salt let salt_len = decoder.read_u8()?.map(|u| u as usize); let salt_len_max = length .map(|u| u as usize) .checked_sub(decoder.index() - start_idx) .map_err(|_| "invalid rdata for salt_len_max")?; let salt_len = salt_len .verify_unwrap(|salt_len| { *salt_len <= salt_len_max.unverified(/*safe in comparison usage*/) }) .map_err(|_| ProtoError::from("salt_len exceeds buffer length"))?; let salt: Vec = decoder.read_vec(salt_len)?.unverified(/*salt is any valid array of bytes*/); // read the hashed_owner_name let hash_len = decoder.read_u8()?.map(|u| u as usize); let hash_len_max = length .map(|u| u as usize) .checked_sub(decoder.index() - start_idx) .map_err(|_| "invalid rdata for hash_len_max")?; let hash_len = hash_len .verify_unwrap(|hash_len| { *hash_len <= hash_len_max.unverified(/*safe in comparison usage*/) }) .map_err(|_| ProtoError::from("hash_len exceeds buffer length"))?; let next_hashed_owner_name: Vec = decoder.read_vec(hash_len)?.unverified(/*will fail in usage if invalid*/); // read the bitmap let bit_map_len = length .map(|u| u as usize) .checked_sub(decoder.index() - start_idx) .map_err(|_| "invalid rdata length in NSEC3")?; let record_types = decode_type_bit_maps(decoder, bit_map_len)?; Ok(Self::new( hash_algorithm, opt_out, iterations, salt, next_hashed_owner_name, record_types, )) } } impl RecordData for NSEC3 { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::NSEC3(csync)) => Ok(csync), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::NSEC3(csync)) => Some(csync), _ => None, } } fn record_type(&self) -> RecordType { RecordType::NSEC3 } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::NSEC3(self)) } } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-3.3), NSEC3, March 2008 /// /// ```text /// 3.3. Presentation Format /// /// The presentation format of the RDATA portion is as follows: /// /// o The Hash Algorithm field is represented as an unsigned decimal /// integer. The value has a maximum of 255. /// /// o The Flags field is represented as an unsigned decimal integer. /// The value has a maximum of 255. /// /// o The Iterations field is represented as an unsigned decimal /// integer. The value is between 0 and 65535, inclusive. /// /// o The Salt Length field is not represented. /// /// o The Salt field is represented as a sequence of case-insensitive /// hexadecimal digits. Whitespace is not allowed within the /// sequence. The Salt field is represented as "-" (without the /// quotes) when the Salt Length field has a value of 0. /// /// o The Hash Length field is not represented. /// /// o The Next Hashed Owner Name field is represented as an unpadded /// sequence of case-insensitive base32 digits, without whitespace. /// /// o The Type Bit Maps field is represented as a sequence of RR type /// mnemonics. When the mnemonic is not known, the TYPE /// representation as described in Section 5 of [RFC3597] MUST be /// used. /// ``` impl fmt::Display for NSEC3 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let salt = if self.salt.is_empty() { "-".to_string() } else { data_encoding::HEXUPPER_PERMISSIVE.encode(&self.salt) }; write!( f, "{alg} {flags} {iterations} {salt} {owner}", alg = u8::from(self.hash_algorithm), flags = self.flags(), iterations = self.iterations, salt = salt, owner = data_encoding::BASE32_NOPAD.encode(&self.next_hashed_owner_name) )?; for ty in &self.type_bit_maps { write!(f, " {ty}")?; } Ok(()) } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; #[test] fn test() { use crate::rr::dnssec::rdata::RecordType; let rdata = NSEC3::new( Nsec3HashAlgorithm::SHA1, true, 2, vec![1, 2, 3, 4, 5], vec![6, 7, 8, 9, 0], vec![ RecordType::A, RecordType::AAAA, RecordType::DS, RecordType::RRSIG, ], ); let mut bytes = Vec::new(); let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); assert!(rdata.emit(&mut encoder).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {bytes:?}"); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let restrict = Restrict::new(bytes.len() as u16); let read_rdata = NSEC3::read_data(&mut decoder, restrict).expect("Decoding error"); assert_eq!(rdata, read_rdata); } #[test] fn test_dups() { use crate::rr::dnssec::rdata::RecordType; let rdata_with_dups = NSEC3::new( Nsec3HashAlgorithm::SHA1, true, 2, vec![1, 2, 3, 4, 5], vec![6, 7, 8, 9, 0], vec![ RecordType::A, RecordType::AAAA, RecordType::DS, RecordType::AAAA, RecordType::RRSIG, ], ); let rdata_wo = NSEC3::new( Nsec3HashAlgorithm::SHA1, true, 2, vec![1, 2, 3, 4, 5], vec![6, 7, 8, 9, 0], vec![ RecordType::A, RecordType::AAAA, RecordType::DS, RecordType::RRSIG, ], ); let mut bytes = Vec::new(); let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); assert!(rdata_with_dups.emit(&mut encoder).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {bytes:?}"); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let restrict = Restrict::new(bytes.len() as u16); let read_rdata = NSEC3::read_data(&mut decoder, restrict).expect("Decoding error"); assert_eq!(rdata_wo, read_rdata); } } hickory-proto-0.24.0/src/rr/dnssec/rdata/nsec3param.rs000064400000000000000000000227341046102023000207160ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! parameters used for the nsec3 hash method use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::{ProtoError, ProtoErrorKind, ProtoResult}, rr::{dnssec::Nsec3HashAlgorithm, RData, RecordData, RecordType}, serialize::binary::*, }; use super::DNSSECRData; /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4), NSEC3, March 2008 /// /// ```text /// 4. The NSEC3PARAM Resource Record /// /// The NSEC3PARAM RR contains the NSEC3 parameters (hash algorithm, /// flags, iterations, and salt) needed by authoritative servers to /// calculate hashed owner names. The presence of an NSEC3PARAM RR at a /// zone apex indicates that the specified parameters may be used by /// authoritative servers to choose an appropriate set of NSEC3 RRs for /// negative responses. The NSEC3PARAM RR is not used by validators or /// resolvers. /// /// If an NSEC3PARAM RR is present at the apex of a zone with a Flags /// field value of zero, then there MUST be an NSEC3 RR using the same /// hash algorithm, iterations, and salt parameters present at every /// hashed owner name in the zone. That is, the zone MUST contain a /// complete set of NSEC3 RRs with the same hash algorithm, iterations, /// and salt parameters. /// /// The owner name for the NSEC3PARAM RR is the name of the zone apex. /// /// The type value for the NSEC3PARAM RR is 51. /// /// The NSEC3PARAM RR RDATA format is class independent and is described /// below. /// /// The class MUST be the same as the NSEC3 RRs to which this RR refers. /// /// 4.2. NSEC3PARAM RDATA Wire Format /// /// The RDATA of the NSEC3PARAM RR is as shown below: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Hash Alg. | Flags | Iterations | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Salt Length | Salt / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// Hash Algorithm is a single octet. /// /// Flags field is a single octet. /// /// Iterations is represented as a 16-bit unsigned integer, with the most /// significant bit first. /// /// Salt Length is represented as an unsigned octet. Salt Length /// represents the length of the following Salt field in octets. If the /// value is zero, the Salt field is omitted. /// /// Salt, if present, is encoded as a sequence of binary octets. The /// length of this field is determined by the preceding Salt Length /// field. /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct NSEC3PARAM { hash_algorithm: Nsec3HashAlgorithm, opt_out: bool, iterations: u16, salt: Vec, } impl NSEC3PARAM { /// Constructs a new NSEC3PARAM RData for use in a Resource Record pub fn new( hash_algorithm: Nsec3HashAlgorithm, opt_out: bool, iterations: u16, salt: Vec, ) -> Self { Self { hash_algorithm, opt_out, iterations, salt, } } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4.1.1), NSEC3, March 2008 /// /// ```text /// 4.1.1. Hash Algorithm /// /// The Hash Algorithm field identifies the cryptographic hash algorithm /// used to construct the hash-value. /// /// The acceptable values are the same as the corresponding field in the /// NSEC3 RR. /// ``` pub fn hash_algorithm(&self) -> Nsec3HashAlgorithm { self.hash_algorithm } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4.1.2), NSEC3, March 2008 /// /// ```text /// 4.1.2. Flag Fields /// /// The Opt-Out flag is not used and is set to zero. /// /// All other flags are reserved for future use, and must be zero. /// /// NSEC3PARAM RRs with a Flags field value other than zero MUST be /// ignored. /// ``` pub fn opt_out(&self) -> bool { self.opt_out } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4.1.3), NSEC3, March 2008 /// /// ```text /// 4.1.3. Iterations /// /// The Iterations field defines the number of additional times the hash /// is performed. /// /// Its acceptable values are the same as the corresponding field in the /// NSEC3 RR. /// ``` pub fn iterations(&self) -> u16 { self.iterations } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4.1.5), NSEC3, March 2008 /// /// ```text /// 4.1.5. Salt /// /// The Salt field is appended to the original owner name before hashing. /// ``` pub fn salt(&self) -> &[u8] { &self.salt } /// flags for encoding pub fn flags(&self) -> u8 { let mut flags: u8 = 0; if self.opt_out { flags |= 0b0000_0001 }; flags } } impl BinEncodable for NSEC3PARAM { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { encoder.emit(self.hash_algorithm().into())?; encoder.emit(self.flags())?; encoder.emit_u16(self.iterations())?; encoder.emit(self.salt().len() as u8)?; encoder.emit_vec(self.salt())?; Ok(()) } } impl<'r> BinDecodable<'r> for NSEC3PARAM { fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult { let hash_algorithm = Nsec3HashAlgorithm::from_u8( decoder.read_u8()?.unverified(/*Algorithm verified as safe*/), )?; let flags: u8 = decoder .read_u8()? .verify_unwrap(|flags| flags & 0b1111_1110 == 0) .map_err(|flags| ProtoError::from(ProtoErrorKind::UnrecognizedNsec3Flags(flags)))?; let opt_out: bool = flags & 0b0000_0001 == 0b0000_0001; let iterations: u16 = decoder.read_u16()?.unverified(/*valid as any u16*/); let salt_len: usize = decoder .read_u8()? .map(|u| u as usize) .verify_unwrap(|salt_len| *salt_len <= decoder.len()) .map_err(|_| ProtoError::from("salt_len exceeds buffer length"))?; let salt: Vec = decoder.read_vec(salt_len)?.unverified(/*valid as any array of u8*/); Ok(Self::new(hash_algorithm, opt_out, iterations, salt)) } } impl RecordData for NSEC3PARAM { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::NSEC3PARAM(csync)) => Ok(csync), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::NSEC3PARAM(csync)) => Some(csync), _ => None, } } fn record_type(&self) -> RecordType { RecordType::NSEC3PARAM } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::NSEC3PARAM(self)) } } /// [RFC 5155](https://tools.ietf.org/html/rfc5155#section-4), NSEC3, March 2008 /// /// ```text /// 4.3. Presentation Format /// /// The presentation format of the RDATA portion is as follows: /// /// o The Hash Algorithm field is represented as an unsigned decimal /// integer. The value has a maximum of 255. /// /// o The Flags field is represented as an unsigned decimal integer. /// The value has a maximum value of 255. /// /// o The Iterations field is represented as an unsigned decimal /// integer. The value is between 0 and 65535, inclusive. /// /// o The Salt Length field is not represented. /// /// o The Salt field is represented as a sequence of case-insensitive /// hexadecimal digits. Whitespace is not allowed within the /// sequence. This field is represented as "-" (without the quotes) /// when the Salt Length field is zero. /// ``` impl fmt::Display for NSEC3PARAM { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let salt = if self.salt.is_empty() { "-".to_string() } else { data_encoding::HEXUPPER_PERMISSIVE.encode(&self.salt) }; write!( f, "{alg} {flags} {iterations} {salt}", alg = u8::from(self.hash_algorithm), flags = self.flags(), iterations = self.iterations, salt = salt ) } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; #[test] fn test() { let rdata = NSEC3PARAM::new(Nsec3HashAlgorithm::SHA1, true, 2, vec![1, 2, 3, 4, 5]); let mut bytes = Vec::new(); let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); assert!(rdata.emit(&mut encoder).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {bytes:?}"); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let read_rdata = NSEC3PARAM::read(&mut decoder).expect("Decoding error"); assert_eq!(rdata, read_rdata); } } hickory-proto-0.24.0/src/rr/dnssec/rdata/rrsig.rs000064400000000000000000000107661046102023000200120ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! RRSIG type and related implementations use std::{fmt, ops::Deref}; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::ProtoResult, rr::{dnssec::Algorithm, Name, RData, RecordData, RecordDataDecodable, RecordType}, serialize::binary::{BinDecoder, BinEncodable, BinEncoder, Restrict}, }; use super::{DNSSECRData, SIG}; /// RRSIG is really a derivation of the original SIG record data. See SIG for more documentation #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct RRSIG(SIG); impl RRSIG { /// Creates a new SIG record data, used for both RRSIG and SIG(0) records. /// /// # Arguments /// /// * `type_covered` - The `RecordType` which this signature covers, should be NULL for SIG(0). /// * `algorithm` - The `Algorithm` used to generate the `signature`. /// * `num_labels` - The number of labels in the name, should be less 1 for *.name labels, /// see `Name::num_labels()`. /// * `original_ttl` - The TTL for the RRSet stored in the zone, should be 0 for SIG(0). /// * `sig_expiration` - Timestamp at which this signature is no longer valid, very important to /// keep this low, < +5 minutes to limit replay attacks. /// * `sig_inception` - Timestamp when this signature was generated. /// * `key_tag` - See the key_tag generation in `rr::dnssec::Signer::key_tag()`. /// * `signer_name` - Domain name of the server which was used to generate the signature. /// * `sig` - signature stored in this record. /// /// # Return value /// /// The new SIG record data. #[allow(clippy::too_many_arguments)] pub fn new( type_covered: RecordType, algorithm: Algorithm, num_labels: u8, original_ttl: u32, sig_expiration: u32, sig_inception: u32, key_tag: u16, signer_name: Name, sig: Vec, ) -> Self { Self(SIG::new( type_covered, algorithm, num_labels, original_ttl, sig_expiration, sig_inception, key_tag, signer_name, sig, )) } } impl Deref for RRSIG { type Target = SIG; fn deref(&self) -> &Self::Target { &self.0 } } impl BinEncodable for RRSIG { /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005 /// /// This is accurate for all currently known name records. /// /// ```text /// 6.2. Canonical RR Form /// /// For the purposes of DNS security, the canonical form of an RR is the /// wire format of the RR where: /// /// ... /// /// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR, /// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX, /// SRV, DNAME, A6, RRSIG, or (rfc6840 removes NSEC), all uppercase /// US-ASCII letters in the DNS names contained within the RDATA are replaced /// by the corresponding lowercase US-ASCII letters; /// ``` fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { self.0.emit(encoder) } } impl<'r> RecordDataDecodable<'r> for RRSIG { fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { SIG::read_data(decoder, length).map(Self) } } impl RecordData for RRSIG { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::RRSIG(csync)) => Ok(csync), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::RRSIG(csync)) => Some(csync), _ => None, } } fn record_type(&self) -> RecordType { RecordType::RRSIG } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::RRSIG(self)) } } impl fmt::Display for RRSIG { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", self.0) } } hickory-proto-0.24.0/src/rr/dnssec/rdata/sig.rs000064400000000000000000000675661046102023000174600ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! signature record for signing queries, updates, and responses use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::*, rr::{dnssec::Algorithm, Name, RData, RecordData, RecordDataDecodable, RecordType}, serialize::binary::*, }; use super::DNSSECRData; /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4), Domain Name System Security Extensions, March 1999 /// /// NOTE: RFC 2535 was obsoleted with 4034+, with the exception of the /// usage for UPDATE, which is what this implementation is for. /// /// ```text /// 4.1 SIG RDATA Format /// /// The RDATA portion of a SIG RR is as shown below. The integrity of /// the RDATA information is protected by the signature field. /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | type covered | algorithm | labels | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | original TTL | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | signature expiration | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | signature inception | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | key tag | | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ signer's name + /// | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-/ /// / / /// / signature / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// ``` /// [RFC 2931](https://tools.ietf.org/html/rfc2931), DNS Request and Transaction Signatures, September 2000 /// /// NOTE: 2931 updates SIG0 to clarify certain particulars... /// /// ```text /// RFC 2931 DNS SIG(0) September 2000 /// /// 3. The SIG(0) Resource Record /// /// The structure of and type number of SIG resource records (RRs) is /// given in [RFC 2535] Section 4.1. However all of Section 4.1.8.1 and /// the parts of Sections 4.2 and 4.3 related to SIG(0) should be /// considered replaced by the material below. Any conflict between [RFC /// 2535] and this document concerning SIG(0) RRs should be resolved in /// favor of this document. /// /// For all transaction SIG(0)s, the signer field MUST be a name of the /// originating host and there MUST be a KEY RR at that name with the /// public key corresponding to the private key used to calculate the /// signature. (The host domain name used may be the inverse IP address /// mapping name for an IP address of the host if the relevant KEY is /// stored there.) /// /// For all SIG(0) RRs, the owner name, class, TTL, and original TTL, are /// meaningless. The TTL fields SHOULD be zero and the CLASS field /// SHOULD be ANY. To conserve space, the owner name SHOULD be root (a /// single zero octet). When SIG(0) authentication on a response is /// desired, that SIG RR MUST be considered the highest priority of any /// additional information for inclusion in the response. If the SIG(0) /// RR cannot be added without causing the message to be truncated, the /// server MUST alter the response so that a SIG(0) can be included. /// This response consists of only the question and a SIG(0) record, and /// has the TC bit set and RCODE 0 (NOERROR). The client should at this /// point retry the request using TCP. /// /// 3.1 Calculating Request and Transaction SIGs /// /// A DNS request may be optionally signed by including one SIG(0)s at /// the end of the query additional information section. Such a SIG is /// identified by having a "type covered" field of zero. It signs the /// preceding DNS request message including DNS header but not including /// the UDP/IP header and before the request RR counts have been adjusted /// for the inclusions of the request SIG(0). /// /// It is calculated by using a "data" (see [RFC 2535], Section 4.1.8) of /// (1) the SIG's RDATA section entirely omitting (not just zeroing) the /// signature subfield itself, (2) the DNS query messages, including DNS /// header, but not the UDP/IP header and before the reply RR counts have /// been adjusted for the inclusion of the SIG(0). That is /// /// data = RDATA | request - SIG(0) /// /// where "|" is concatenation and RDATA is the RDATA of the SIG(0) being /// calculated less the signature itself. /// /// Similarly, a SIG(0) can be used to secure a response and the request /// that produced it. Such transaction signatures are calculated by /// using a "data" of (1) the SIG's RDATA section omitting the signature /// itself, (2) the entire DNS query message that produced this response, /// including the query's DNS header but not its UDP/IP header, and (3) /// the entire DNS response message, including DNS header but not the /// UDP/IP header and before the response RR counts have been adjusted /// for the inclusion of the SIG(0). /// /// That is /// /// data = RDATA | full query | response - SIG(0) /// /// where "|" is concatenation and RDATA is the RDATA of the SIG(0) being /// calculated less the signature itself. /// /// Verification of a response SIG(0) (which is signed by the server host /// key, not the zone key) by the requesting resolver shows that the /// query and response were not tampered with in transit, that the /// response corresponds to the intended query, and that the response /// comes from the queried server. /// /// In the case of a DNS message via TCP, a SIG(0) on the first data /// packet is calculated with "data" as above and for each subsequent /// packet, it is calculated as follows: /// /// data = RDATA | DNS payload - SIG(0) | previous packet /// /// where "|" is concatenations, RDATA is as above, and previous packet /// is the previous DNS payload including DNS header and the SIG(0) but /// not the TCP/IP header. Support of SIG(0) for TCP is OPTIONAL. As an /// alternative, TSIG may be used after, if necessary, setting up a key /// with TKEY [RFC 2930]. /// /// Except where needed to authenticate an update, TKEY, or similar /// privileged request, servers are not required to check a request /// SIG(0). /// /// Note: requests and responses can either have a single TSIG or one /// SIG(0) but not both a TSIG and a SIG(0). /// /// 3.2 Processing Responses and SIG(0) RRs /// /// If a SIG RR is at the end of the additional information section of a /// response and has a type covered of zero, it is a transaction /// signature covering the response and the query that produced the /// response. For TKEY responses, it MUST be checked and the message /// rejected if the checks fail unless otherwise specified for the TKEY /// mode in use. For all other responses, it MAY be checked and the /// message rejected if the checks fail. /// /// If a response's SIG(0) check succeed, such a transaction /// authentication SIG does NOT directly authenticate the validity any /// data-RRs in the message. However, it authenticates that they were /// sent by the queried server and have not been diddled. (Only a proper /// SIG(0) RR signed by the zone or a key tracing its authority to the /// zone or to static resolver configuration can directly authenticate /// /// data-RRs, depending on resolver policy.) If a resolver or server does /// not implement transaction and/or request SIGs, it MUST ignore them /// without error where they are optional and treat them as failing where /// they are required. /// /// 3.3 SIG(0) Lifetime and Expiration /// /// The inception and expiration times in SIG(0)s are for the purpose of /// resisting replay attacks. They should be set to form a time bracket /// such that messages outside that bracket can be ignored. In IP /// networks, this time bracket should not normally extend further than 5 /// minutes into the past and 5 minutes into the future. /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct SIG { type_covered: RecordType, algorithm: Algorithm, num_labels: u8, original_ttl: u32, sig_expiration: u32, sig_inception: u32, key_tag: u16, signer_name: Name, sig: Vec, } impl SIG { /// Creates a new SIG record data, used for both RRSIG and SIG(0) records. /// /// # Arguments /// /// * `type_covered` - The `RecordType` which this signature covers, should be NULL for SIG(0). /// * `algorithm` - The `Algorithm` used to generate the `signature`. /// * `num_labels` - The number of labels in the name, should be less 1 for *.name labels, /// see `Name::num_labels()`. /// * `original_ttl` - The TTL for the RRSet stored in the zone, should be 0 for SIG(0). /// * `sig_expiration` - Timestamp at which this signature is no longer valid, very important to /// keep this low, < +5 minutes to limit replay attacks. /// * `sig_inception` - Timestamp when this signature was generated. /// * `key_tag` - See the key_tag generation in `rr::dnssec::Signer::key_tag()`. /// * `signer_name` - Domain name of the server which was used to generate the signature. /// * `sig` - signature stored in this record. /// /// # Return value /// /// The new SIG record data. #[allow(clippy::too_many_arguments)] pub fn new( type_covered: RecordType, algorithm: Algorithm, num_labels: u8, original_ttl: u32, sig_expiration: u32, sig_inception: u32, key_tag: u16, signer_name: Name, sig: Vec, ) -> Self { Self { type_covered, algorithm, num_labels, original_ttl, sig_expiration, sig_inception, key_tag, signer_name, sig, } } /// Add actual signature value to existing SIG record data. /// /// # Arguments /// /// * `signature` - signature to be stored in this record. /// /// # Return value /// /// The new SIG record data. pub fn set_sig(self, signature: Vec) -> Self { Self { type_covered: self.type_covered, algorithm: self.algorithm, num_labels: self.num_labels, original_ttl: self.original_ttl, sig_expiration: self.sig_expiration, sig_inception: self.sig_inception, key_tag: self.key_tag, signer_name: self.signer_name, sig: signature, } } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.1), Domain Name System Security Extensions, March 1999 /// /// ```text /// 4.1.1 Type Covered Field /// /// The "type covered" is the type of the other RRs covered by this SIG. /// ``` pub fn type_covered(&self) -> RecordType { self.type_covered } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.2), Domain Name System Security Extensions, March 1999 /// /// ```text /// 4.1.2 Algorithm Number Field /// /// This octet is as described in section 3.2. /// ``` pub fn algorithm(&self) -> Algorithm { self.algorithm } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.3), Domain Name System Security Extensions, March 1999 /// /// ```text /// 4.1.3 Labels Field /// /// The "labels" octet is an unsigned count of how many labels there are /// in the original SIG RR owner name not counting the null label for /// root and not counting any initial "*" for a wildcard. If a secured /// retrieval is the result of wild card substitution, it is necessary /// for the resolver to use the original form of the name in verifying /// the digital signature. This field makes it easy to determine the /// original form. /// /// If, on retrieval, the RR appears to have a longer name than indicated /// by "labels", the resolver can tell it is the result of wildcard /// substitution. If the RR owner name appears to be shorter than the /// labels count, the SIG RR must be considered corrupt and ignored. The /// maximum number of labels allowed in the current DNS is 127 but the /// entire octet is reserved and would be required should DNS names ever /// be expanded to 255 labels. The following table gives some examples. /// The value of "labels" is at the top, the retrieved owner name on the /// left, and the table entry is the name to use in signature /// verification except that "bad" means the RR is corrupt. /// /// labels= | 0 | 1 | 2 | 3 | 4 | /// --------+-----+------+--------+----------+----------+ /// .| . | bad | bad | bad | bad | /// d.| *. | d. | bad | bad | bad | /// c.d.| *. | *.d. | c.d. | bad | bad | /// b.c.d.| *. | *.d. | *.c.d. | b.c.d. | bad | /// a.b.c.d.| *. | *.d. | *.c.d. | *.b.c.d. | a.b.c.d. | /// ``` pub fn num_labels(&self) -> u8 { self.num_labels } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.4), Domain Name System Security Extensions, March 1999 /// /// ```text /// 4.1.4 Original TTL Field /// /// The "original TTL" field is included in the RDATA portion to avoid /// (1) authentication problems that caching servers would otherwise /// cause by decrementing the real TTL field and (2) security problems /// that unscrupulous servers could otherwise cause by manipulating the /// real TTL field. This original TTL is protected by the signature /// while the current TTL field is not. /// /// NOTE: The "original TTL" must be restored into the covered RRs when /// the signature is verified (see Section 8). This generally implies /// that all RRs for a particular type, name, and class, that is, all the /// RRs in any particular RRset, must have the same TTL to start with. /// ``` pub fn original_ttl(&self) -> u32 { self.original_ttl } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.5), Domain Name System Security Extensions, March 1999 /// /// ```text /// 4.1.5 Signature Expiration and Inception Fields /// /// The SIG is valid from the "signature inception" time until the /// "signature expiration" time. Both are unsigned numbers of seconds /// since the start of 1 January 1970, GMT, ignoring leap seconds. (See /// also Section 4.4.) Ring arithmetic is used as for DNS SOA serial /// numbers [RFC 1982] which means that these times can never be more /// than about 68 years in the past or the future. This means that these /// times are ambiguous modulo ~136.09 years. However there is no /// security flaw because keys are required to be changed to new random /// keys by [RFC 2541] at least every five years. This means that the /// probability that the same key is in use N*136.09 years later should /// be the same as the probability that a random guess will work. /// /// A SIG RR may have an expiration time numerically less than the /// inception time if the expiration time is near the 32 bit wrap around /// point and/or the signature is long lived. /// /// (To prevent misordering of network requests to update a zone /// dynamically, monotonically increasing "signature inception" times may /// be necessary.) /// /// A secure zone must be considered changed for SOA serial number /// purposes not only when its data is updated but also when new SIG RRs /// are inserted (ie, the zone or any part of it is re-signed). /// ``` pub fn sig_expiration(&self) -> u32 { self.sig_expiration } /// see `get_sig_expiration` pub fn sig_inception(&self) -> u32 { self.sig_inception } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.6), Domain Name System Security Extensions, March 1999 /// /// ```text /// 4.1.6 Key Tag Field /// /// The "key Tag" is a two octet quantity that is used to efficiently /// select between multiple keys which may be applicable and thus check /// that a public key about to be used for the computationally expensive /// effort to check the signature is possibly valid. For algorithm 1 /// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two /// octets of the public key modulus needed to decode the signature /// field. That is to say, the most significant 16 of the least /// significant 24 bits of the modulus in network (big endian) order. For /// all other algorithms, including private algorithms, it is calculated /// as a simple checksum of the KEY RR as described in Appendix C. /// ``` pub fn key_tag(&self) -> u16 { self.key_tag } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.7), Domain Name System Security Extensions, March 1999 /// /// ```text /// 4.1.7 Signer's Name Field /// /// The "signer's name" field is the domain name of the signer generating /// the SIG RR. This is the owner name of the public KEY RR that can be /// used to verify the signature. It is frequently the zone which /// contained the RRset being authenticated. Which signers should be /// authorized to sign what is a significant resolver policy question as /// discussed in Section 6. The signer's name may be compressed with /// standard DNS name compression when being transmitted over the /// network. /// ``` pub fn signer_name(&self) -> &Name { &self.signer_name } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.8), Domain Name System Security Extensions, March 1999 /// /// ```text /// 4.1.8 Signature Field /// /// The actual signature portion of the SIG RR binds the other RDATA /// fields to the RRset of the "type covered" RRs with that owner name /// and class. This covered RRset is thereby authenticated. To /// accomplish this, a data sequence is constructed as follows: /// /// data = RDATA | RR(s)... /// /// where "|" is concatenation, /// /// RDATA is the wire format of all the RDATA fields in the SIG RR itself /// (including the canonical form of the signer's name) before but not /// including the signature, and /// /// RR(s) is the RRset of the RR(s) of the type covered with the same /// owner name and class as the SIG RR in canonical form and order as /// defined in Section 8. /// /// How this data sequence is processed into the signature is algorithm /// dependent. These algorithm dependent formats and procedures are /// described in separate documents (Section 3.2). /// /// SIGs SHOULD NOT be included in a zone for any "meta-type" such as /// ANY, AXFR, etc. (but see section 5.6.2 with regard to IXFR). /// ``` pub fn sig(&self) -> &[u8] { &self.sig } } impl BinEncodable for SIG { /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005 /// /// This is accurate for all currently known name records. /// /// ```text /// 6.2. Canonical RR Form /// /// For the purposes of DNS security, the canonical form of an RR is the /// wire format of the RR where: /// /// ... /// /// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR, /// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX, /// SRV, DNAME, A6, RRSIG, or (rfc6840 removes NSEC), all uppercase /// US-ASCII letters in the DNS names contained within the RDATA are replaced /// by the corresponding lowercase US-ASCII letters; /// ``` fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { let is_canonical_names = encoder.is_canonical_names(); self.type_covered().emit(encoder)?; self.algorithm().emit(encoder)?; encoder.emit(self.num_labels())?; encoder.emit_u32(self.original_ttl())?; encoder.emit_u32(self.sig_expiration())?; encoder.emit_u32(self.sig_inception())?; encoder.emit_u16(self.key_tag())?; self.signer_name() .emit_with_lowercase(encoder, is_canonical_names)?; encoder.emit_vec(self.sig())?; Ok(()) } } impl<'r> RecordDataDecodable<'r> for SIG { fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { let start_idx = decoder.index(); // TODO should we verify here? or elsewhere... let type_covered = RecordType::read(decoder)?; let algorithm = Algorithm::read(decoder)?; let num_labels = decoder.read_u8()?.unverified(/*technically valid as any u8*/); let original_ttl = decoder.read_u32()?.unverified(/*valid as any u32*/); let sig_expiration = decoder.read_u32()?.unverified(/*valid as any u32, in practice should be in the future*/); let sig_inception = decoder.read_u32()?.unverified(/*valid as any u32, in practice should be before expiration*/); let key_tag = decoder.read_u16()?.unverified(/*valid as any u16*/); let signer_name = Name::read(decoder)?; // read the signature, this will vary buy key size let sig_len = length .map(|u| u as usize) .checked_sub(decoder.index() - start_idx) .map_err(|_| ProtoError::from("invalid rdata length in SIG"))? .unverified(/*used only as length safely*/); let sig = decoder .read_vec(sig_len)? .unverified(/*will fail in usage if invalid*/); Ok(Self::new( type_covered, algorithm, num_labels, original_ttl, sig_expiration, sig_inception, key_tag, signer_name, sig, )) } } impl RecordData for SIG { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::SIG(csync)) => Ok(csync), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::SIG(csync)) => Some(csync), _ => None, } } fn record_type(&self) -> RecordType { RecordType::SIG } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::SIG(self)) } } /// specifically for outputting the RData for an RRSIG, with signer_name in canonical form #[allow(clippy::too_many_arguments)] pub fn emit_pre_sig( encoder: &mut BinEncoder<'_>, type_covered: RecordType, algorithm: Algorithm, num_labels: u8, original_ttl: u32, sig_expiration: u32, sig_inception: u32, key_tag: u16, signer_name: &Name, ) -> ProtoResult<()> { type_covered.emit(encoder)?; algorithm.emit(encoder)?; encoder.emit(num_labels)?; encoder.emit_u32(original_ttl)?; encoder.emit_u32(sig_expiration)?; encoder.emit_u32(sig_inception)?; encoder.emit_u16(key_tag)?; signer_name.emit_as_canonical(encoder, true)?; Ok(()) } /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-7.2), Domain Name System Security Extensions, March 1999 /// /// ```text /// 7.2 Presentation of SIG RRs /// /// A data SIG RR may be represented as a single logical line in a zone /// data file [RFC 1033] but there are some special considerations as /// described below. (It does not make sense to include a transaction or /// request authenticating SIG RR in a file as they are a transient /// authentication that covers data including an ephemeral transaction /// number and so must be calculated in real time.) /// /// There is no particular problem with the signer, covered type, and /// times. The time fields appears in the form YYYYMMDDHHMMSS where YYYY /// is the year, the first MM is the month number (01-12), DD is the day /// of the month (01-31), HH is the hour in 24 hours notation (00-23), /// the second MM is the minute (00-59), and SS is the second (00-59). /// /// The original TTL field appears as an unsigned integer. /// /// If the original TTL, which applies to the type signed, is the same as /// the TTL of the SIG RR itself, it may be omitted. The date field /// which follows it is larger than the maximum possible TTL so there is /// no ambiguity. /// /// The "labels" field appears as an unsigned integer. /// /// The key tag appears as an unsigned number. /// /// However, the signature itself can be very long. It is the last data /// field and is represented in base 64 (see Appendix A) and may be /// divided up into any number of white space separated substrings, down /// to single base 64 digits, which are concatenated to obtain the full /// signature. These substrings can be split between lines using the /// standard parenthesis. /// /// foo.nil. SIG NXT 1 2 ( ;type-cov=NXT, alg=1, labels=2 /// 19970102030405 ;signature expiration /// 19961211100908 ;signature inception /// 2143 ;key identifier /// foo.nil. ;signer /// AIYADP8d3zYNyQwW2EM4wXVFdslEJcUx/fxkfBeH1El4ixPFhpfHFElxbvKoWmvjDTCm /// fiYy2X+8XpFjwICHc398kzWsTMKlxovpz2FnCTM= ;signature (640 bits) /// ``` impl fmt::Display for SIG { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{ty_covered} {alg} {num_labels} {original_ttl} {expire} {inception} {tag} {signer} {sig}", ty_covered = self.type_covered, alg = self.algorithm, num_labels = self.num_labels, original_ttl = self.original_ttl, expire = self.sig_expiration, inception = self.sig_inception, tag = self.key_tag, signer = self.signer_name, sig = data_encoding::BASE64.encode(&self.sig) ) } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; #[test] fn test() { use std::str::FromStr; let rdata = SIG::new( RecordType::NULL, Algorithm::RSASHA256, 0, 0, 2, 1, 5, Name::from_str("www.example.com").unwrap(), vec![ 0, 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, 29, 31, ], // 32 bytes for SHA256 ); let mut bytes = Vec::new(); let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); assert!(rdata.emit(&mut encoder).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {bytes:?}"); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let restrict = Restrict::new(bytes.len() as u16); let read_rdata = SIG::read_data(&mut decoder, restrict).expect("Decoding error"); assert_eq!(rdata, read_rdata); } } hickory-proto-0.24.0/src/rr/dnssec/rdata/tsig.rs000064400000000000000000001103051046102023000176200ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! TSIG for secret key authentication of transaction #![allow(clippy::use_self)] use std::{convert::TryInto, fmt}; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::{ error::{ProtoError, ProtoErrorKind, ProtoResult}, op::{Header, Message, Query}, rr::{ dns_class::DNSClass, dnssec::rdata::DNSSECRData, rdata::sshfp, record_data::RData, record_type::RecordType, Name, Record, RecordData, RecordDataDecodable, }, serialize::binary::*, }; /// [RFC 8945, Secret Key Transaction Authentication for DNS](https://tools.ietf.org/html/rfc8945#section-4.2) /// /// ```text /// 4.2. TSIG Record Format /// /// The fields of the TSIG RR are described below. All multi-octet /// integers in the record are sent in network byte order (see /// Section 2.3.2 of [RFC1035]). /// /// NAME: The name of the key used, in domain name syntax. The name /// should reflect the names of the hosts and uniquely identify the /// key among a set of keys these two hosts may share at any given /// time. For example, if hosts A.site.example and B.example.net /// share a key, possibilities for the key name include /// .A.site.example, .B.example.net, and /// .A.site.example.B.example.net. It should be possible for more /// than one key to be in simultaneous use among a set of interacting /// hosts. This allows for periodic key rotation as per best /// operational practices, as well as algorithm agility as indicated /// by [RFC7696]. /// /// The name may be used as a local index to the key involved, but it /// is recommended that it be globally unique. Where a key is just /// shared between two hosts, its name actually need only be /// meaningful to them, but it is recommended that the key name be /// mnemonic and incorporate the names of participating agents or /// resources as suggested above. /// /// TYPE: This MUST be TSIG (250: Transaction SIGnature). /// /// CLASS: This MUST be ANY. /// /// TTL: This MUST be 0. /// /// RDLENGTH: (variable) /// /// RDATA: The RDATA for a TSIG RR consists of a number of fields, /// described below: /// /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Algorithm Name / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// | Time Signed +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | Fudge | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | MAC Size | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ MAC / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Original ID | Error | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Other Len | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Other Data / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// The contents of the RDATA fields are: /// /// Algorithm Name: /// an octet sequence identifying the TSIG algorithm in the domain /// name syntax. (Allowed names are listed in Table 3.) The name is /// stored in the DNS name wire format as described in [RFC1034]. As /// per [RFC3597], this name MUST NOT be compressed. /// /// Time Signed: /// an unsigned 48-bit integer containing the time the message was /// signed as seconds since 00:00 on 1970-01-01 UTC, ignoring leap /// seconds. /// /// Fudge: /// an unsigned 16-bit integer specifying the allowed time difference /// in seconds permitted in the Time Signed field. /// /// MAC Size: /// an unsigned 16-bit integer giving the length of the MAC field in /// octets. Truncation is indicated by a MAC Size less than the size /// of the keyed hash produced by the algorithm specified by the /// Algorithm Name. /// /// MAC: /// a sequence of octets whose contents are defined by the TSIG /// algorithm used, possibly truncated as specified by the MAC Size. /// The length of this field is given by the MAC Size. Calculation of /// the MAC is detailed in Section 4.3. /// /// Original ID: /// an unsigned 16-bit integer holding the message ID of the original /// request message. For a TSIG RR on a request, it is set equal to /// the DNS message ID. In a TSIG attached to a response -- or in /// cases such as the forwarding of a dynamic update request -- the /// field contains the ID of the original DNS request. /// /// Error: /// in responses, an unsigned 16-bit integer containing the extended /// RCODE covering TSIG processing. In requests, this MUST be zero. /// /// Other Len: /// an unsigned 16-bit integer specifying the length of the Other Data /// field in octets. /// /// Other Data: /// additional data relevant to the TSIG record. In responses, this /// will be empty (i.e., Other Len will be zero) unless the content of /// the Error field is BADTIME, in which case it will be a 48-bit /// unsigned integer containing the server's current time as the /// number of seconds since 00:00 on 1970-01-01 UTC, ignoring leap /// seconds (see Section 5.2.3). This document assigns no meaning to /// its contents in requests. /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct TSIG { algorithm: TsigAlgorithm, time: u64, fudge: u16, mac: Vec, oid: u16, error: u16, other: Vec, } /// Algorithm used to authenticate communication /// /// [RFC8945 Secret Key Transaction Authentication for DNS](https://tools.ietf.org/html/rfc8945#section-6) /// ```text /// +==========================+================+=================+ /// | Algorithm Name | Implementation | Use | /// +==========================+================+=================+ /// | HMAC-MD5.SIG-ALG.REG.INT | MAY | MUST NOT | /// +--------------------------+----------------+-----------------+ /// | gss-tsig | MAY | MAY | /// +--------------------------+----------------+-----------------+ /// | hmac-sha1 | MUST | NOT RECOMMENDED | /// +--------------------------+----------------+-----------------+ /// | hmac-sha224 | MAY | MAY | /// +--------------------------+----------------+-----------------+ /// | hmac-sha256 | MUST | RECOMMENDED | /// +--------------------------+----------------+-----------------+ /// | hmac-sha256-128 | MAY | MAY | /// +--------------------------+----------------+-----------------+ /// | hmac-sha384 | MAY | MAY | /// +--------------------------+----------------+-----------------+ /// | hmac-sha384-192 | MAY | MAY | /// +--------------------------+----------------+-----------------+ /// | hmac-sha512 | MAY | MAY | /// +--------------------------+----------------+-----------------+ /// | hmac-sha512-256 | MAY | MAY | /// +--------------------------+----------------+-----------------+ /// ``` #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum TsigAlgorithm { /// HMAC-MD5.SIG-ALG.REG.INT (not supported for cryptographic operations) HmacMd5, /// gss-tsig (not supported for cryptographic operations) Gss, /// hmac-sha1 (not supported for cryptographic operations) HmacSha1, /// hmac-sha224 (not supported for cryptographic operations) HmacSha224, /// hmac-sha256 HmacSha256, /// hmac-sha256-128 (not supported for cryptographic operations) HmacSha256_128, /// hmac-sha384 HmacSha384, /// hmac-sha384-192 (not supported for cryptographic operations) HmacSha384_192, /// hmac-sha512 HmacSha512, /// hmac-sha512-256 (not supported for cryptographic operations) HmacSha512_256, /// Unkown algorithm Unknown(Name), } impl TSIG { /// Constructs a new TSIG /// /// [RFC 8945, Secret Key Transaction Authentication for DNS](https://tools.ietf.org/html/rfc8945#section-4.1) /// /// ```text /// 4.1. TSIG RR Type /// /// To provide secret key authentication, we use an RR type whose /// mnemonic is TSIG and whose type code is 250. TSIG is a meta-RR and /// MUST NOT be cached. TSIG RRs are used for authentication between DNS /// entities that have established a shared secret key. TSIG RRs are /// dynamically computed to cover a particular DNS transaction and are /// not DNS RRs in the usual sense. /// /// As the TSIG RRs are related to one DNS request/response, there is no /// value in storing or retransmitting them; thus, the TSIG RR is /// discarded once it has been used to authenticate a DNS message. /// ``` pub fn new( algorithm: TsigAlgorithm, time: u64, fudge: u16, mac: Vec, oid: u16, error: u16, other: Vec, ) -> Self { Self { algorithm, time, fudge, mac, oid, error, other, } } /// Returns the Mac in this TSIG pub fn mac(&self) -> &[u8] { &self.mac } /// Returns the time this TSIG was generated at pub fn time(&self) -> u64 { self.time } /// Returns the max delta from `time` for remote to accept the signature pub fn fudge(&self) -> u16 { self.fudge } /// Returns the algorithm used for the authentication code pub fn algorithm(&self) -> &TsigAlgorithm { &self.algorithm } /// Emit TSIG RR and RDATA as used for computing MAC /// /// ```text /// 4.3.3. TSIG Variables /// /// Also included in the digest is certain information present in the /// TSIG RR. Adding this data provides further protection against an /// attempt to interfere with the message. /// /// +============+================+====================================+ /// | Source | Field Name | Notes | /// +============+================+====================================+ /// | TSIG RR | NAME | Key name, in canonical wire format | /// +------------+----------------+------------------------------------+ /// | TSIG RR | CLASS | MUST be ANY | /// +------------+----------------+------------------------------------+ /// | TSIG RR | TTL | MUST be 0 | /// +------------+----------------+------------------------------------+ /// | TSIG RDATA | Algorithm Name | in canonical wire format | /// +------------+----------------+------------------------------------+ /// | TSIG RDATA | Time Signed | in network byte order | /// +------------+----------------+------------------------------------+ /// | TSIG RDATA | Fudge | in network byte order | /// +------------+----------------+------------------------------------+ /// | TSIG RDATA | Error | in network byte order | /// +------------+----------------+------------------------------------+ /// | TSIG RDATA | Other Len | in network byte order | /// +------------+----------------+------------------------------------+ /// | TSIG RDATA | Other Data | exactly as transmitted | /// +------------+----------------+------------------------------------+ /// ``` pub fn emit_tsig_for_mac( &self, encoder: &mut BinEncoder<'_>, key_name: &Name, ) -> ProtoResult<()> { key_name.emit_as_canonical(encoder, true)?; DNSClass::ANY.emit(encoder)?; encoder.emit_u32(0)?; // TTL self.algorithm.emit(encoder)?; encoder.emit_u16((self.time >> 32) as u16)?; encoder.emit_u32(self.time as u32)?; encoder.emit_u16(self.fudge)?; encoder.emit_u16(self.error)?; encoder.emit_u16(self.other.len() as u16)?; encoder.emit_vec(&self.other)?; Ok(()) } /// Add actual MAC value to existing TSIG record data. /// /// # Arguments /// /// * `mac` - mac to be stored in this record. pub fn set_mac(self, mac: Vec) -> Self { Self { mac, ..self } } } impl BinEncodable for TSIG { /// Write the RData from the given Encoder /// /// ```text /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Algorithm Name / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// | Time Signed +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | Fudge | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | MAC Size | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ MAC / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Original ID | Error | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Other Len | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Other Data / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// ``` fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { self.algorithm.emit(encoder)?; encoder.emit_u16( (self.time >> 32) .try_into() .map_err(|_| ProtoError::from("invalid time, overflow 48 bit counter in TSIG"))?, )?; encoder.emit_u32(self.time as u32)?; // this cast is supposed to truncate encoder.emit_u16(self.fudge)?; encoder.emit_u16( self.mac .len() .try_into() .map_err(|_| ProtoError::from("invalid mac, longer than 65535 B in TSIG"))?, )?; encoder.emit_vec(&self.mac)?; encoder.emit_u16(self.oid)?; encoder.emit_u16(self.error)?; encoder.emit_u16(self.other.len().try_into().map_err(|_| { ProtoError::from("invalid other_buffer, longer than 65535 B in TSIG") })?)?; encoder.emit_vec(&self.other)?; Ok(()) } } impl<'r> RecordDataDecodable<'r> for TSIG { /// Read the RData from the given Decoder /// /// ```text /// 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// / Algorithm Name / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | /// | Time Signed +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | | Fudge | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | MAC Size | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ MAC / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Original ID | Error | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Other Len | / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Other Data / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// ``` fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict) -> ProtoResult { let end_idx = length.map(|rdl| rdl as usize) .checked_add(decoder.index()) .map_err(|_| ProtoError::from("rdata end position overflow"))? // no legal message is long enough to trigger that .unverified(/*used only as length safely*/); let algorithm = TsigAlgorithm::read(decoder)?; let time_high = decoder.read_u16()?.unverified(/*valid as any u16*/) as u64; let time_low = decoder.read_u32()?.unverified(/*valid as any u32*/) as u64; let time = (time_high << 32) | time_low; let fudge = decoder.read_u16()?.unverified(/*valid as any u16*/); let mac_size = decoder .read_u16()? .verify_unwrap(|&size| decoder.index() + size as usize + 6 /* 3 u16 */ <= end_idx) .map_err(|_| ProtoError::from("invalid mac length in TSIG"))?; let mac = decoder.read_vec(mac_size as usize)?.unverified(/*valid as any vec of the right size*/); let oid = decoder.read_u16()?.unverified(/*valid as any u16*/); let error = decoder.read_u16()?.unverified(/*valid as any u16*/); let other_len = decoder .read_u16()? .verify_unwrap(|&size| decoder.index() + size as usize == end_idx) .map_err(|_| ProtoError::from("invalid other length in TSIG"))?; let other = decoder.read_vec(other_len as usize)?.unverified(/*valid as any vec of the right size*/); Ok(Self { algorithm, time, fudge, mac, oid, error, other, }) } } impl RecordData for TSIG { fn try_from_rdata(data: RData) -> Result { match data { RData::DNSSEC(DNSSECRData::TSIG(csync)) => Ok(csync), _ => Err(data), } } fn try_borrow(data: &RData) -> Option<&Self> { match data { RData::DNSSEC(DNSSECRData::TSIG(csync)) => Some(csync), _ => None, } } fn record_type(&self) -> RecordType { RecordType::TSIG } fn into_rdata(self) -> RData { RData::DNSSEC(DNSSECRData::TSIG(self)) } } // Does not appear to have a normalized text representation impl fmt::Display for TSIG { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, "{algorithm} {time} {fudge} {mac} {oid} {error} {other}", algorithm = self.algorithm, time = self.time, fudge = self.fudge, mac = sshfp::HEX.encode(&self.mac), oid = self.oid, error = self.error, other = sshfp::HEX.encode(&self.other), ) } } impl TsigAlgorithm { /// Return DNS name for the algorithm pub fn to_name(&self) -> Name { use TsigAlgorithm::*; match self { HmacMd5 => Name::from_ascii("HMAC-MD5.SIG-ALG.REG.INT"), Gss => Name::from_ascii("gss-tsig"), HmacSha1 => Name::from_ascii("hmac-sha1"), HmacSha224 => Name::from_ascii("hmac-sha224"), HmacSha256 => Name::from_ascii("hmac-sha256"), HmacSha256_128 => Name::from_ascii("hmac-sha256-128"), HmacSha384 => Name::from_ascii("hmac-sha384"), HmacSha384_192 => Name::from_ascii("hmac-sha384-192"), HmacSha512 => Name::from_ascii("hmac-sha512"), HmacSha512_256 => Name::from_ascii("hmac-sha512-256"), Unknown(name) => Ok(name.clone()), }.unwrap(/* should not fail with static strings*/) } /// Write the Algorithm to the given encoder pub fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { self.to_name().emit_as_canonical(encoder, true)?; Ok(()) } /// Read the Algorithm from the given Encoder pub fn read(decoder: &mut BinDecoder<'_>) -> ProtoResult { let mut name = Name::read(decoder)?; name.set_fqdn(false); Ok(Self::from_name(name)) } /// Convert a DNS name to an Algorithm pub fn from_name(name: Name) -> Self { use TsigAlgorithm::*; match name.to_ascii().as_str() { "HMAC-MD5.SIG-ALG.REG.INT" => HmacMd5, "gss-tsig" => Gss, "hmac-sha1" => HmacSha1, "hmac-sha224" => HmacSha224, "hmac-sha256" => HmacSha256, "hmac-sha256-128" => HmacSha256_128, "hmac-sha384" => HmacSha384, "hmac-sha384-192" => HmacSha384_192, "hmac-sha512" => HmacSha512, "hmac-sha512-256" => HmacSha512_256, _ => Unknown(name), } } // TODO: remove this once hickory-client no longer has dnssec feature enabled by default #[cfg(not(any(feature = "ring", feature = "openssl")))] #[doc(hidden)] #[allow(clippy::unimplemented)] pub fn mac_data(&self, _key: &[u8], _message: &[u8]) -> ProtoResult> { unimplemented!("one of dnssec-ring or dnssec-openssl features must be enabled") } /// Compute the Message Authentication Code using key and algorithm /// /// Supported algorithm are HmacSha256, HmacSha384, HmacSha512 and HmacSha512_256 /// Other algorithm return an error. #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn mac_data(&self, key: &[u8], message: &[u8]) -> ProtoResult> { use ring::hmac; use TsigAlgorithm::*; let key = match self { HmacSha256 => hmac::Key::new(hmac::HMAC_SHA256, key), HmacSha384 => hmac::Key::new(hmac::HMAC_SHA384, key), HmacSha512 => hmac::Key::new(hmac::HMAC_SHA512, key), _ => return Err(ProtoErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()), }; let mac = hmac::sign(&key, message); let res = mac.as_ref().to_vec(); Ok(res) } /// Compute the Message Authentication Code using key and algorithm /// /// Supported algorithm are HmacSha256, HmacSha384, HmacSha512 and HmacSha512_256 /// Other algorithm return an error. #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] pub fn mac_data(&self, key: &[u8], message: &[u8]) -> ProtoResult> { use openssl::{hash::MessageDigest, pkey::PKey, sign::Signer}; use TsigAlgorithm::*; let key = PKey::hmac(key)?; let mut signer = match self { HmacSha256 => Signer::new(MessageDigest::sha256(), &key)?, HmacSha384 => Signer::new(MessageDigest::sha384(), &key)?, HmacSha512 => Signer::new(MessageDigest::sha512(), &key)?, _ => return Err(ProtoErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()), }; signer.update(message)?; signer.sign_to_vec().map_err(|e| e.into()) } // TODO: remove this once hickory-client no longer has dnssec feature enabled by default #[cfg(not(any(feature = "ring", feature = "openssl")))] #[doc(hidden)] #[allow(clippy::unimplemented)] pub fn verify_mac(&self, _key: &[u8], _message: &[u8], _tag: &[u8]) -> ProtoResult<()> { unimplemented!("one of dnssec-ring or dnssec-openssl features must be enabled") } /// Verifies the hmac tag against the given key and this algorithm. /// /// This is both faster than independently creating the MAC and also constant time preventing timing attacks #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn verify_mac(&self, key: &[u8], message: &[u8], tag: &[u8]) -> ProtoResult<()> { use ring::hmac; use TsigAlgorithm::*; let key = match self { HmacSha256 => hmac::Key::new(hmac::HMAC_SHA256, key), HmacSha384 => hmac::Key::new(hmac::HMAC_SHA384, key), HmacSha512 => hmac::Key::new(hmac::HMAC_SHA512, key), _ => return Err(ProtoErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()), }; hmac::verify(&key, message, tag).map_err(|_| ProtoErrorKind::HmacInvalid().into()) } /// Verifies the hmac tag against the given key and this algorithm. /// /// This is constant time preventing timing attacks #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] pub fn verify_mac(&self, key: &[u8], message: &[u8], tag: &[u8]) -> ProtoResult<()> { use openssl::memcmp; let hmac = self.mac_data(key, message)?; if memcmp::eq(&hmac, tag) { Ok(()) } else { Err(ProtoErrorKind::HmacInvalid().into()) } } // TODO: remove this once hickory-client no longer has dnssec feature enabled by default #[cfg(not(any(feature = "ring", feature = "openssl")))] #[doc(hidden)] #[allow(clippy::unimplemented)] pub fn output_len(&self) -> ProtoResult { unimplemented!("one of dnssec-ring or dnssec-openssl features must be enabled") } /// Return length in bytes of the algorithms output #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn output_len(&self) -> ProtoResult { use ring::hmac; use TsigAlgorithm::*; let len = match self { HmacSha256 => hmac::HMAC_SHA256.digest_algorithm().output_len, HmacSha384 => hmac::HMAC_SHA384.digest_algorithm().output_len, HmacSha512 => hmac::HMAC_SHA512.digest_algorithm().output_len, _ => return Err(ProtoErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()), }; Ok(len) } /// Return length in bytes of the algorithms output #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] pub fn output_len(&self) -> ProtoResult { use openssl::hash::MessageDigest; use TsigAlgorithm::*; let len = match self { HmacSha256 => MessageDigest::sha256().size(), HmacSha384 => MessageDigest::sha384().size(), HmacSha512 => MessageDigest::sha512().size(), _ => return Err(ProtoErrorKind::TsigUnsupportedMacAlgorithm(self.clone()).into()), }; Ok(len) } /// Return `true` if cryptographic operations needed for using this algorithm are supported, /// `false` otherwise /// /// ## Supported /// /// - HmacSha256 /// - HmacSha384 /// - HmacSha512 /// - HmacSha512_256 pub fn supported(&self) -> bool { use TsigAlgorithm::*; matches!(self, HmacSha256 | HmacSha384 | HmacSha512) } } impl fmt::Display for TsigAlgorithm { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", self.to_name()) } } /// Return the byte-message to be authenticated with a TSIG /// /// # Arguments /// /// * `previous_hash` - hash of previous message in case of message chaining, or of query in case /// of response. Should be None for query /// * `message` - the message to authenticate. Should not be modified after calling message_tbs /// except for adding the TSIG record /// * `pre_tsig` - TSIG rrdata, possibly with missing mac. Should not be modified in any other way /// after calling message_tbs /// * `key_name` - name of they key, should be the same as the name known by the remove /// server/client pub fn message_tbs( previous_hash: Option<&[u8]>, message: &M, pre_tsig: &TSIG, key_name: &Name, ) -> ProtoResult> { let mut buf: Vec = Vec::with_capacity(512); let mut encoder: BinEncoder<'_> = BinEncoder::with_mode(&mut buf, EncodeMode::Normal); if let Some(previous_hash) = previous_hash { encoder.emit_u16(previous_hash.len() as u16)?; encoder.emit_vec(previous_hash)?; }; message.emit(&mut encoder)?; pre_tsig.emit_tsig_for_mac(&mut encoder, key_name)?; Ok(buf) } /// Return the byte-message that would have been used to generate a TSIG /// /// # Arguments /// /// * `previous_hash` - hash of previous message in case of message chaining, or of query in case /// of response. Should be None for query /// * `message` - the byte-message to authenticate, with included TSIG pub fn signed_bitmessage_to_buf( previous_hash: Option<&[u8]>, message: &[u8], first_message: bool, ) -> ProtoResult<(Vec, Record)> { let mut decoder = BinDecoder::new(message); // remove the tsig from Additional count let mut header = Header::read(&mut decoder)?; let adc = header.additional_count(); if adc > 0 { header.set_additional_count(adc - 1); } else { return Err(ProtoError::from( "missing tsig from response that must be authenticated", )); } // keep position of data start let start_data = message.len() - decoder.len(); let count = header.query_count(); for _ in 0..count { Query::read(&mut decoder)?; } // read all records except for the last one (tsig) let record_count = header.answer_count() as usize + header.name_server_count() as usize + header.additional_count() as usize; Message::read_records(&mut decoder, record_count, false)?; // keep position of data end let end_data = message.len() - decoder.len(); // parse a tsig record let sig = Record::read(&mut decoder)?; let tsig = if let (RecordType::TSIG, Some(RData::DNSSEC(DNSSECRData::TSIG(tsig_data)))) = (sig.record_type(), sig.data()) { tsig_data } else { return Err(ProtoError::from("signature is not tsig")); }; header.set_id(tsig.oid); let mut buf = Vec::with_capacity(message.len()); let mut encoder = BinEncoder::new(&mut buf); // prepend previous Mac if it exists if let Some(previous_hash) = previous_hash { encoder.emit_u16(previous_hash.len() as u16)?; encoder.emit_vec(previous_hash)?; } // emit header without tsig header.emit(&mut encoder)?; // copy all records verbatim, without decompressing it encoder.emit_vec(&message[start_data..end_data])?; if first_message { // emit the tsig pseudo-record for first message tsig.emit_tsig_for_mac(&mut encoder, sig.name())?; } else { // emit only time and fudge for followings encoder.emit_u16((tsig.time >> 32) as u16)?; encoder.emit_u32(tsig.time as u32)?; encoder.emit_u16(tsig.fudge)?; } Ok((buf, sig)) } /// Helper function to make a TSIG record from the name of the key, and the TSIG RData pub fn make_tsig_record(name: Name, rdata: TSIG) -> Record { // https://tools.ietf.org/html/rfc8945#section-4.2 let mut tsig = Record::new(); // NAME: The name of the key used, in domain name syntax tsig.set_name(name) // TYPE: This MUST be TSIG (250: Transaction SIGnature). .set_record_type(RecordType::TSIG) // CLASS: This MUST be ANY. .set_dns_class(DNSClass::ANY) // TTL: This MUST be 0. .set_ttl(0) .set_data(Some(DNSSECRData::TSIG(rdata).into())); tsig } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; use crate::rr::Record; fn test_encode_decode(rdata: TSIG) { let mut bytes = Vec::new(); let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); rdata.emit(&mut encoder).expect("failed to emit tsig"); let bytes = encoder.into_bytes(); println!("bytes: {bytes:?}"); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let read_rdata = TSIG::read_data(&mut decoder, Restrict::new(bytes.len() as u16)) .expect("failed to read back"); assert_eq!(rdata, read_rdata); } #[test] fn test_encode_decode_tsig() { test_encode_decode(TSIG::new( TsigAlgorithm::HmacSha256, 0, 300, vec![0, 1, 2, 3], 0, 0, vec![4, 5, 6, 7], )); test_encode_decode(TSIG::new( TsigAlgorithm::HmacSha384, 123456789, 60, vec![9, 8, 7, 6, 5, 4], 1, 2, vec![], )); test_encode_decode(TSIG::new( TsigAlgorithm::Unknown(Name::from_ascii("unknown_algorithm").unwrap()), 123456789, 60, vec![], 1, 2, vec![0, 1, 2, 3, 4, 5, 6], )); } #[test] fn test_sign_encode() { let mut message = Message::new(); message.add_answer(Record::new()); let key_name = Name::from_ascii("some.name").unwrap(); let pre_tsig = TSIG::new( TsigAlgorithm::HmacSha256, 12345, 60, vec![], message.id(), 0, vec![], ); let tbs = message_tbs(None, &message, &pre_tsig, &key_name).unwrap(); let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec()); let tsig = make_tsig_record(key_name, pre_tsig); message.add_tsig(tsig); let message_byte = message.to_bytes().unwrap(); let tbv = signed_bitmessage_to_buf(None, &message_byte, true) .unwrap() .0; assert_eq!(tbs, tbv); } #[test] fn test_sign_encode_id_changed() { let mut message = Message::new(); message.set_id(123).add_answer(Record::new()); let key_name = Name::from_ascii("some.name").unwrap(); let pre_tsig = TSIG::new( TsigAlgorithm::HmacSha256, 12345, 60, vec![], message.id(), 0, vec![], ); let tbs = message_tbs(None, &message, &pre_tsig, &key_name).unwrap(); let pre_tsig = pre_tsig.set_mac(b"some signature".to_vec()); let tsig = make_tsig_record(key_name, pre_tsig); message.add_tsig(tsig); let message_byte = message.to_bytes().unwrap(); let mut message = Message::from_bytes(&message_byte).unwrap(); message.set_id(456); // simulate the request id being changed due to request forwarding let message_byte = message.to_bytes().unwrap(); let tbv = signed_bitmessage_to_buf(None, &message_byte, true) .unwrap() .0; assert_eq!(tbs, tbv); // sign and verify let key = &[0, 1, 2, 3, 4]; let tag = TsigAlgorithm::HmacSha256.mac_data(key, &tbv).unwrap(); TsigAlgorithm::HmacSha256 .verify_mac(key, &tbv, &tag) .expect("did not verify") } } hickory-proto-0.24.0/src/rr/dnssec/roots/19036.rsa000064400000000000000000000004041046102023000175460ustar 00000000000000 UfB膻Lڄ~mza&U,m! p(TMˏ]т4:q ,"C_12QmQOB 5%UC=g#~)_R%顎.V4te,3V;s쀉n- s[Nhs 3#$|-:C8.K!b^W}{RA'n@8ќ.jdK(u!` I͞jC>RMb=hickory-proto-0.24.0/src/rr/dnssec/roots/20326.rsa000064400000000000000000000004041046102023000175400ustar 00000000000000 91US 2sΉmowczF,GYD&^%ry MW?/-5U i) ,mvՆ{d|?8āR qY2S|y(h/!h֫U+6wnJu/w:)>ˍW52T gEG:TfhL |,y*模Q˛_cgL GP$Q5{hickory-proto-0.24.0/src/rr/dnssec/roots/README.md000064400000000000000000000072351046102023000176450ustar 00000000000000# Generating Roots The process for getting the current key-signing-key, ksk, roots is by means of a tool in `utils`. The tool can be run via `cargo run --bin get-root-ksks`, it will output data that looks like this: ```console $ cargo run --bin get-root-ksks Compiling hickory-util v0.3.0-alpha.1 (file:///Users/benjaminfry/Development/rust/hickory-dns/util) Finished dev [unoptimized + debuginfo] target(s) in 6.48s Running `/Users/benjaminfry/Development/rust/hickory-dns/target/debug/get-root-ksks` found: tag: 20326 info: DNSKEY { zone_key: true, secure_entry_point: true, revoke: false, algorithm: RSASHA256, public_key: [3, 1, 0, 1, 172, 255, 180, 9, 188, 201, 57, 248, 49, 247,161, 229, 236, 136, 247, 165, 146, 85, 236, 83, 4, 11, 228, 50, 2, 115, 144, 164, 206, 137, 109, 111, 144, 134, 243, 197, 225, 119, 251, 254, 17, 129, 99, 170, 236, 122, 241, 70, 44,71, 148, 89, 68, 196, 226, 192, 38, 190, 94, 152, 187, 205, 237, 37, 151, 130, 114, 225, 227, 224, 121, 197, 9, 77, 87, 63, 14, 131, 201, 47, 2, 179, 45, 53, 19, 177, 85, 11, 130, 105, 41, 200, 13, 208, 249, 44, 172, 150, 109, 23, 118, 159, 213, 134, 123, 100, 124, 63, 56, 2, 154, 189, 196, 129, 82, 235, 143, 32, 113, 89, 236, 197, 210, 50, 199, 193, 83, 124, 121, 244, 183, 172, 40, 255, 17, 104, 47, 33, 104, 27, 246, 214, 171, 165, 85, 3, 43, 246, 249, 240, 54, 190, 178, 170, 165, 179, 119, 141, 110, 235, 251, 166, 191, 158, 161, 145, 190, 74, 176, 202, 234, 117, 158, 47, 119, 58, 31, 144, 41, 199, 62, 203, 141, 87, 53, 185, 50, 29, 176, 133, 241, 184, 226, 216, 3, 143, 226, 148, 25, 146, 84, 140, 238, 13, 103, 221, 69, 71, 225, 29, 214, 58, 249, 201, 252, 28, 84, 102, 251, 104, 76, 240, 9, 215, 25, 124, 44, 247, 158, 121, 42, 181, 1, 230, 168, 161, 202, 81, 154, 242, 203, 155, 95, 99, 103, 233, 76, 13, 71, 80, 36, 81, 53, 123, 225, 181] } found: tag: 19036 info: DNSKEY { zone_key: true, secure_entry_point: true, revoke: false, algorithm: RSASHA256, public_key: [3, 1, 0, 1, 168, 0, 32, 169, 85, 102, 186, 66, 232, 134, 187, 128, 76, 218, 132, 228, 126, 245, 109, 189, 122, 236, 97, 38, 21, 85, 44, 236, 144, 109, 33, 22, 208, 239, 32, 112, 40, 197, 21, 84, 20, 77, 254, 175, 231, 199, 203, 143, 0, 93, 209, 130, 52, 19, 58, 192, 113, 10, 129, 24, 44, 225, 253, 20, 173, 34, 131, 188, 131, 67, 95, 157, 242, 246, 49, 50, 81, 147, 26, 23, 109, 240, 218, 81, 229, 79, 66, 230, 4, 134, 13,251, 53, 149, 128, 37, 15, 85, 156, 197, 67, 196, 255, 213, 28, 190, 61, 232, 207, 208, 103, 25, 35, 127, 159, 196, 126, 231, 41, 218, 6, 131, 95, 164, 82, 232, 37, 233, 161, 142, 188, 46, 203, 207, 86, 52, 116, 101, 44, 51, 207, 86, 169, 3, 59, 205, 245, 217, 115, 18, 23, 151, 236, 128, 137, 4, 27, 110, 3, 161, 183, 45, 10, 115, 91, 152, 78, 3, 104, 115, 9, 51, 35, 36, 242, 124, 45, 186, 133, 233, 219, 21, 232, 58, 1, 67, 56, 46, 151, 75, 6, 33, 193, 142, 98, 94, 206, 201, 7, 87, 125, 158, 123, 173, 233, 82, 65, 168, 30, 187, 232, 169, 1, 212, 211, 39, 110, 64, 177, 20, 192, 162, 230, 252, 56, 209, 156, 46, 106, 171, 2, 100, 75, 40, 19, 245, 117, 252, 33, 96, 30, 13, 238, 73, 205, 158, 233, 106, 67, 16, 62, 82, 77, 98, 135, 61] } ``` The tags, represent key_tags, as generated when signing with keys and storing in RRSIG records. The current known key_tags are 20326 and 19036. The keys will be output to `/tmp/{key_tag}.{type}`, eg `/tmp/20326.rsa`. See [https://www.icann.org/dns-resolvers-checking-current-trust-anchors](Checking the Current Trust Anchors in DNS Validating Resolvers) for additional help. Once generated, copy the key to `proto/src/rr/dnssec/roots/` and then add to `proto/src/rr/dnssec/trust_anchor.rs`. ## FAQ ### Can't this be better? Yes. We should get the signatures for these keys and verify them before adding them.hickory-proto-0.24.0/src/rr/dnssec/rsa_public_key.rs000064400000000000000000000023321046102023000205520ustar 00000000000000// Copyright 2017 Brian Smith // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use crate::error::*; pub(crate) struct RSAPublicKey<'a> { n: &'a [u8], e: &'a [u8], } impl<'a> RSAPublicKey<'a> { pub(crate) fn try_from(encoded: &'a [u8]) -> ProtoResult> { let (e_len_len, e_len) = match encoded.first() { Some(&0) if encoded.len() >= 3 => { (3, (usize::from(encoded[1]) << 8) | usize::from(encoded[2])) } Some(e_len) if *e_len != 0 => (1, usize::from(*e_len)), _ => { return Err("bad public key".into()); } }; if encoded.len() < e_len_len + e_len { return Err("bad public key".into()); }; let (e, n) = encoded[e_len_len..].split_at(e_len); Ok(Self { n, e }) } pub(crate) fn n(&self) -> &[u8] { self.n } pub(crate) fn e(&self) -> &[u8] { self.e } } hickory-proto-0.24.0/src/rr/dnssec/signer.rs000064400000000000000000001103161046102023000170500ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! signer is a structure for performing many of the signing processes of the DNSSEC specification use tracing::debug; #[cfg(feature = "dnssec")] use std::time::Duration; #[cfg(feature = "dnssec")] use crate::{ error::DnsSecResult, rr::{ dnssec::{ rdata::{DNSSECRData, DNSKEY, KEY, SIG}, tbs, Algorithm, KeyPair, Private, TBS, }, {DNSClass, Name, RData, RecordType}, }, serialize::binary::BinEncoder, }; use crate::{ error::{ProtoErrorKind, ProtoResult}, op::{Message, MessageFinalizer, MessageVerifier}, rr::Record, serialize::binary::BinEncodable, }; /// Use for performing signing and validation of DNSSEC based components. The SigSigner can be used for singing requests and responses with SIG0, or DNSSEC RRSIG records. The format is based on the SIG record type. /// /// TODO: warning this struct and it's impl are under high volatility, expect breaking changes /// /// [RFC 4035](https://tools.ietf.org/html/rfc4035), DNSSEC Protocol Modifications, March 2005 /// /// ```text /// 5.3. Authenticating an RRset with an RRSIG RR /// /// A validator can use an RRSIG RR and its corresponding DNSKEY RR to /// attempt to authenticate RRsets. The validator first checks the RRSIG /// RR to verify that it covers the RRset, has a valid time interval, and /// identifies a valid DNSKEY RR. The validator then constructs the /// canonical form of the signed data by appending the RRSIG RDATA /// (excluding the Signature Field) with the canonical form of the /// covered RRset. Finally, the validator uses the public key and /// signature to authenticate the signed data. Sections 5.3.1, 5.3.2, /// and 5.3.3 describe each step in detail. /// /// 5.3.1. Checking the RRSIG RR Validity /// /// A security-aware resolver can use an RRSIG RR to authenticate an /// RRset if all of the following conditions hold: /// /// o The RRSIG RR and the RRset MUST have the same owner name and the /// same class. /// /// o The RRSIG RR's Signer's Name field MUST be the name of the zone /// that contains the RRset. /// /// o The RRSIG RR's Type Covered field MUST equal the RRset's type. /// /// o The number of labels in the RRset owner name MUST be greater than /// or equal to the value in the RRSIG RR's Labels field. /// /// o The validator's notion of the current time MUST be less than or /// equal to the time listed in the RRSIG RR's Expiration field. /// /// o The validator's notion of the current time MUST be greater than or /// equal to the time listed in the RRSIG RR's Inception field. /// /// o The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST /// match the owner name, algorithm, and key tag for some DNSKEY RR in /// the zone's apex DNSKEY RRset. /// /// o The matching DNSKEY RR MUST be present in the zone's apex DNSKEY /// RRset, and MUST have the Zone Flag bit (DNSKEY RDATA Flag bit 7) /// set. /// /// It is possible for more than one DNSKEY RR to match the conditions /// above. In this case, the validator cannot predetermine which DNSKEY /// RR to use to authenticate the signature, and it MUST try each /// matching DNSKEY RR until either the signature is validated or the /// validator has run out of matching public keys to try. /// /// Note that this authentication process is only meaningful if the /// validator authenticates the DNSKEY RR before using it to validate /// signatures. The matching DNSKEY RR is considered to be authentic if: /// /// o the apex DNSKEY RRset containing the DNSKEY RR is considered /// authentic; or /// /// o the RRset covered by the RRSIG RR is the apex DNSKEY RRset itself, /// and the DNSKEY RR either matches an authenticated DS RR from the /// parent zone or matches a trust anchor. /// /// 5.3.2. Reconstructing the Signed Data /// /// Once the RRSIG RR has met the validity requirements described in /// Section 5.3.1, the validator has to reconstruct the original signed /// data. The original signed data includes RRSIG RDATA (excluding the /// Signature field) and the canonical form of the RRset. Aside from /// being ordered, the canonical form of the RRset might also differ from /// the received RRset due to DNS name compression, decremented TTLs, or /// wildcard expansion. The validator should use the following to /// reconstruct the original signed data: /// /// signed_data = RRSIG_RDATA | RR(1) | RR(2)... where /// /// "|" denotes concatenation /// /// RRSIG_RDATA is the wire format of the RRSIG RDATA fields /// with the Signature field excluded and the Signer's Name /// in canonical form. /// /// RR(i) = name | type | class | OrigTTL | RDATA length | RDATA /// /// name is calculated according to the function below /// /// class is the RRset's class /// /// type is the RRset type and all RRs in the class /// /// OrigTTL is the value from the RRSIG Original TTL field /// /// All names in the RDATA field are in canonical form /// /// The set of all RR(i) is sorted into canonical order. /// /// To calculate the name: /// let rrsig_labels = the value of the RRSIG Labels field /// /// let fqdn = RRset's fully qualified domain name in /// canonical form /// /// let fqdn_labels = Label count of the fqdn above. /// /// if rrsig_labels = fqdn_labels, /// name = fqdn /// /// if rrsig_labels < fqdn_labels, /// name = "*." | the rightmost rrsig_label labels of the /// fqdn /// /// if rrsig_labels > fqdn_labels /// the RRSIG RR did not pass the necessary validation /// checks and MUST NOT be used to authenticate this /// RRset. /// /// The canonical forms for names and RRsets are defined in [RFC4034]. /// /// NSEC RRsets at a delegation boundary require special processing. /// There are two distinct NSEC RRsets associated with a signed delegated /// name. One NSEC RRset resides in the parent zone, and specifies which /// RRsets are present at the parent zone. The second NSEC RRset resides /// at the child zone and identifies which RRsets are present at the apex /// in the child zone. The parent NSEC RRset and child NSEC RRset can /// always be distinguished as only a child NSEC RR will indicate that an /// SOA RRset exists at the name. When reconstructing the original NSEC /// RRset for the delegation from the parent zone, the NSEC RRs MUST NOT /// be combined with NSEC RRs from the child zone. When reconstructing /// the original NSEC RRset for the apex of the child zone, the NSEC RRs /// MUST NOT be combined with NSEC RRs from the parent zone. /// /// Note that each of the two NSEC RRsets at a delegation point has a /// corresponding RRSIG RR with an owner name matching the delegated /// name, and each of these RRSIG RRs is authoritative data associated /// with the same zone that contains the corresponding NSEC RRset. If /// necessary, a resolver can tell these RRSIG RRs apart by checking the /// Signer's Name field. /// /// 5.3.3. Checking the Signature /// /// Once the resolver has validated the RRSIG RR as described in Section /// 5.3.1 and reconstructed the original signed data as described in /// Section 5.3.2, the validator can attempt to use the cryptographic /// signature to authenticate the signed data, and thus (finally!) /// authenticate the RRset. /// /// The Algorithm field in the RRSIG RR identifies the cryptographic /// algorithm used to generate the signature. The signature itself is /// contained in the Signature field of the RRSIG RDATA, and the public /// key used to verify the signature is contained in the Public Key field /// of the matching DNSKEY RR(s) (found in Section 5.3.1). [RFC4034] /// provides a list of algorithm types and provides pointers to the /// documents that define each algorithm's use. /// /// Note that it is possible for more than one DNSKEY RR to match the /// conditions in Section 5.3.1. In this case, the validator can only /// determine which DNSKEY RR is correct by trying each matching public /// key until the validator either succeeds in validating the signature /// or runs out of keys to try. /// /// If the Labels field of the RRSIG RR is not equal to the number of /// labels in the RRset's fully qualified owner name, then the RRset is /// either invalid or the result of wildcard expansion. The resolver /// MUST verify that wildcard expansion was applied properly before /// considering the RRset to be authentic. Section 5.3.4 describes how /// to determine whether a wildcard was applied properly. /// /// If other RRSIG RRs also cover this RRset, the local resolver security /// policy determines whether the resolver also has to test these RRSIG /// RRs and how to resolve conflicts if these RRSIG RRs lead to differing /// results. /// /// If the resolver accepts the RRset as authentic, the validator MUST /// set the TTL of the RRSIG RR and each RR in the authenticated RRset to /// a value no greater than the minimum of: /// /// o the RRset's TTL as received in the response; /// /// o the RRSIG RR's TTL as received in the response; /// /// o the value in the RRSIG RR's Original TTL field; and /// /// o the difference of the RRSIG RR's Signature Expiration time and the /// current time. /// /// 5.3.4. Authenticating a Wildcard Expanded RRset Positive Response /// /// If the number of labels in an RRset's owner name is greater than the /// Labels field of the covering RRSIG RR, then the RRset and its /// covering RRSIG RR were created as a result of wildcard expansion. /// Once the validator has verified the signature, as described in /// Section 5.3, it must take additional steps to verify the non- /// existence of an exact match or closer wildcard match for the query. /// Section 5.4 discusses these steps. /// /// Note that the response received by the resolver should include all /// NSEC RRs needed to authenticate the response (see Section 3.1.3). /// ``` #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub struct SigSigner { // TODO: this should really be a trait and generic struct over KEY and DNSKEY key_rdata: RData, key: KeyPair, algorithm: Algorithm, signer_name: Name, sig_duration: Duration, is_zone_signing_key: bool, } /// Placeholder type for when OpenSSL and *ring* are disabled; enable OpenSSL and Ring for support #[cfg(not(feature = "dnssec"))] #[allow(missing_copy_implementations)] pub struct SigSigner; /// See [`SigSigner`](crate::rr::dnssec::SigSigner) #[deprecated(note = "renamed to SigSigner")] pub type Signer = SigSigner; #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] impl SigSigner { /// Version of Signer for verifying RRSIGs and SIG0 records. /// /// # Arguments /// /// * `key_rdata` - the DNSKEY and public key material /// * `key` - the private key for signing, unless validating, where just the public key is necessary /// * `signer_name` - name in the zone to which this DNSKEY is bound /// * `sig_duration` - time period for which this key is valid, 0 when verifying /// * `is_zone_update_auth` - this key may be used for updating the zone pub fn dnssec( key_rdata: DNSKEY, key: KeyPair, signer_name: Name, sig_duration: Duration, ) -> Self { let algorithm = key_rdata.algorithm(); let is_zone_signing_key = key_rdata.zone_key(); Self { key_rdata: key_rdata.into(), key, algorithm, signer_name, sig_duration, is_zone_signing_key, } } /// Version of Signer for verifying RRSIGs and SIG0 records. /// /// # Arguments /// /// * `key_rdata` - the KEY and public key material /// * `key` - the private key for signing, unless validating, where just the public key is necessary /// * `signer_name` - name in the zone to which this DNSKEY is bound /// * `is_zone_update_auth` - this key may be used for updating the zone pub fn sig0(key_rdata: KEY, key: KeyPair, signer_name: Name) -> Self { let algorithm = key_rdata.algorithm(); Self { key_rdata: key_rdata.into(), key, algorithm, signer_name, // can be Duration::ZERO after min Rust version 1.53 sig_duration: Duration::new(0, 0), is_zone_signing_key: false, } } /// Version of Signer for signing RRSIGs and SIG0 records. #[deprecated(note = "use SIG0 or DNSSEC constructors")] pub fn new( algorithm: Algorithm, key: KeyPair, signer_name: Name, sig_duration: Duration, is_zone_signing_key: bool, _: bool, ) -> Self { let dnskey = key .to_dnskey(algorithm) .expect("something went wrong, use one of the SIG0 or DNSSEC constructors"); Self { key_rdata: dnskey.into(), key, algorithm, signer_name, sig_duration, is_zone_signing_key, } } /// Return the key used for validation/signing pub fn key(&self) -> &KeyPair { &self.key } /// Returns the duration that this signature is valid for pub fn sig_duration(&self) -> Duration { self.sig_duration } /// A hint to the DNSKey associated with this Signer can be used to sign/validate records in the zone pub fn is_zone_signing_key(&self) -> bool { self.is_zone_signing_key } /// Signs a hash. /// /// This will panic if the `key` is not a private key and can be used for signing. /// /// # Arguments /// /// * `hash` - the hashed resource record set, see `rrset_tbs`. /// /// # Return value /// /// The signature, ready to be stored in an `RData::RRSIG`. pub fn sign(&self, tbs: &TBS) -> ProtoResult> { self.key .sign(self.algorithm, tbs) .map_err(|e| ProtoErrorKind::Msg(format!("signing error: {e}")).into()) } /// Returns the algorithm this Signer will use to either sign or validate a signature pub fn algorithm(&self) -> Algorithm { self.algorithm } /// The name of the signing entity, e.g. the DNS server name. /// /// This should match the name on key in the zone. pub fn signer_name(&self) -> &Name { &self.signer_name } // TODO: move this to DNSKEY/KEY? /// The key tag is calculated as a hash to more quickly lookup a DNSKEY. /// /// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987 /// /// ```text /// RFC 2535 DNS Security Extensions March 1999 /// /// 4.1.6 Key Tag Field /// /// The "key Tag" is a two octet quantity that is used to efficiently /// select between multiple keys which may be applicable and thus check /// that a public key about to be used for the computationally expensive /// effort to check the signature is possibly valid. For algorithm 1 /// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two /// octets of the public key modulus needed to decode the signature /// field. That is to say, the most significant 16 of the least /// significant 24 bits of the modulus in network (big endian) order. For /// all other algorithms, including private algorithms, it is calculated /// as a simple checksum of the KEY RR as described in Appendix C. /// /// Appendix C: Key Tag Calculation /// /// The key tag field in the SIG RR is just a means of more efficiently /// selecting the correct KEY RR to use when there is more than one KEY /// RR candidate available, for example, in verifying a signature. It is /// possible for more than one candidate key to have the same tag, in /// which case each must be tried until one works or all fail. The /// following reference implementation of how to calculate the Key Tag, /// for all algorithms other than algorithm 1, is in ANSI C. It is coded /// for clarity, not efficiency. (See section 4.1.6 for how to determine /// the Key Tag of an algorithm 1 key.) /// /// /* assumes int is at least 16 bits /// first byte of the key tag is the most significant byte of return /// value /// second byte of the key tag is the least significant byte of /// return value /// */ /// /// int keytag ( /// /// unsigned char key[], /* the RDATA part of the KEY RR */ /// unsigned int keysize, /* the RDLENGTH */ /// ) /// { /// long int ac; /* assumed to be 32 bits or larger */ /// /// for ( ac = 0, i = 0; i < keysize; ++i ) /// ac += (i&1) ? key[i] : key[i]<<8; /// ac += (ac>>16) & 0xFFFF; /// return ac & 0xFFFF; /// } /// ``` pub fn calculate_key_tag(&self) -> ProtoResult { let mut bytes: Vec = Vec::with_capacity(512); { let mut e = BinEncoder::new(&mut bytes); self.key_rdata.emit(&mut e)?; } Ok(DNSKEY::calculate_key_tag_internal(&bytes)) } /// Signs the given message, returning the signature bytes. /// /// # Arguments /// /// * `message` - the message to sign /// /// [rfc2535](https://tools.ietf.org/html/rfc2535#section-4.1.8.1), Domain Name System Security Extensions, 1999 /// /// ```text /// 4.1.8.1 Calculating Transaction and Request SIGs /// /// A response message from a security aware server may optionally /// contain a special SIG at the end of the additional information /// section to authenticate the transaction. /// /// This SIG has a "type covered" field of zero, which is not a valid RR /// type. It is calculated by using a "data" (see Section 4.1.8) of the /// entire preceding DNS reply message, including DNS header but not the /// IP header and before the reply RR counts have been adjusted for the /// inclusion of any transaction SIG, concatenated with the entire DNS /// query message that produced this response, including the query's DNS /// header and any request SIGs but not its IP header. That is /// /// data = full response (less transaction SIG) | full query /// /// Verification of the transaction SIG (which is signed by the server /// host key, not the zone key) by the requesting resolver shows that the /// query and response were not tampered with in transit, that the /// response corresponds to the intended query, and that the response /// comes from the queried server. /// /// A DNS request may be optionally signed by including one or more SIGs /// at the end of the query. Such SIGs are identified by having a "type /// covered" field of zero. They sign the preceding DNS request message /// including DNS header but not including the IP header or any request /// SIGs at the end and before the request RR counts have been adjusted /// for the inclusions of any request SIG(s). /// /// WARNING: Request SIGs are unnecessary for any currently defined /// request other than update [RFC 2136, 2137] and will cause some old /// DNS servers to give an error return or ignore a query. However, such /// SIGs may in the future be needed for other requests. /// /// Except where needed to authenticate an update or similar privileged /// request, servers are not required to check request SIGs. /// ``` /// --- /// /// NOTE: In classic RFC style, this is unclear, it implies that each SIG record is not included in /// the Additional record count, but this makes it more difficult to process and calculate more /// than one SIG0 record. Annoyingly, it means that the Header is signed with different material /// (i.e. additional record count - #SIG0 records), so the exact header sent is NOT the header /// being verified. /// /// --- pub fn sign_message(&self, message: &Message, pre_sig0: &SIG) -> ProtoResult> { tbs::message_tbs(message, pre_sig0).and_then(|tbs| self.sign(&tbs)) } /// Extracts a public KEY from this Signer pub fn to_dnskey(&self) -> DnsSecResult { // TODO: this interface should allow for setting if this is a secure entry point vs. ZSK self.key .to_public_bytes() .map(|bytes| DNSKEY::new(self.is_zone_signing_key, true, false, self.algorithm, bytes)) } /// Test that this key is capable of signing and verifying data pub fn test_key(&self) -> DnsSecResult<()> { // use proto::rr::dnssec::PublicKey; // // TODO: why doesn't this work for ecdsa_256 and 384? // let test_data = TBS::from(b"DEADBEEF" as &[u8]); // let signature = self.sign(&test_data).map_err(|e| {println!("failed to sign, {:?}", e); e})?; // let pk = self.key.to_public_key()?; // pk.verify(self.algorithm, test_data.as_ref(), &signature).map_err(|e| {println!("failed to verify, {:?}", e); e})?; Ok(()) } } impl MessageFinalizer for SigSigner { #[cfg(feature = "dnssec")] fn finalize_message( &self, message: &Message, current_time: u32, ) -> ProtoResult<(Vec, Option)> { debug!("signing message: {message:?}"); let key_tag: u16 = self.calculate_key_tag()?; // this is based on RFCs 2535, 2931 and 3007 // 'For all SIG(0) RRs, the owner name, class, TTL, and original TTL, are // meaningless.' - 2931 let mut sig0 = Record::new(); // The TTL fields SHOULD be zero sig0.set_ttl(0); // The CLASS field SHOULD be ANY sig0.set_dns_class(DNSClass::ANY); // The owner name SHOULD be root (a single zero octet). sig0.set_name(Name::root()); let num_labels = sig0.name().num_labels(); let expiration_time: u32 = current_time + (5 * 60); // +5 minutes in seconds sig0.set_rr_type(RecordType::SIG); let pre_sig0 = SIG::new( // type covered in SIG(0) is 0 which is what makes this SIG0 vs a standard SIG RecordType::ZERO, self.algorithm(), num_labels, // see above, original_ttl is meaningless, The TTL fields SHOULD be zero 0, // recommended time is +5 minutes from now, to prevent timing attacks, 2 is probably good expiration_time, // current time, this should be UTC // unsigned numbers of seconds since the start of 1 January 1970, GMT current_time, key_tag, // can probably get rid of this clone if the ownership is correct self.signer_name().clone(), Vec::new(), ); let signature: Vec = self.sign_message(message, &pre_sig0)?; sig0.set_data(Some(RData::DNSSEC(DNSSECRData::SIG( pre_sig0.set_sig(signature), )))); Ok((vec![sig0], None)) } #[cfg(not(feature = "dnssec"))] fn finalize_message( &self, _: &Message, _: u32, ) -> ProtoResult<(Vec, Option)> { Err( ProtoErrorKind::Message("the ring or openssl feature must be enabled for signing") .into(), ) } } #[cfg(test)] #[cfg(feature = "openssl")] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use openssl::bn::BigNum; use openssl::pkey::Private; use openssl::rsa::Rsa; use crate::op::{Message, Query}; use crate::rr::dnssec::rdata::key::KeyUsage; use crate::rr::dnssec::rdata::{DNSSECRData, RRSIG, SIG}; use crate::rr::dnssec::*; use crate::rr::rdata::NS; use crate::rr::{DNSClass, Name, Record, RecordType}; use super::*; fn assert_send_and_sync() {} #[test] fn test_send_and_sync() { assert_send_and_sync::(); } fn pre_sig0(signer: &SigSigner, inception_time: u32, expiration_time: u32) -> SIG { SIG::new( // type covered in SIG(0) is 0 which is what makes this SIG0 vs a standard SIG RecordType::ZERO, signer.algorithm(), 0, // see above, original_ttl is meaningless, The TTL fields SHOULD be zero 0, // recommended time is +5 minutes from now, to prevent timing attacks, 2 is probably good expiration_time, // current time, this should be UTC // unsigned numbers of seconds since the start of 1 January 1970, GMT inception_time, signer.calculate_key_tag().unwrap(), // can probably get rid of this clone if the ownership is correct signer.signer_name().clone(), Vec::new(), ) } #[test] fn test_sign_and_verify_message_sig0() { let origin: Name = Name::parse("example.com.", None).unwrap(); let mut question: Message = Message::new(); let mut query: Query = Query::new(); query.set_name(origin); question.add_query(query); let rsa = Rsa::generate(2048).unwrap(); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key.to_sig0key(Algorithm::RSASHA256).unwrap(); let signer = SigSigner::sig0(sig0key.clone(), key, Name::root()); let pre_sig0 = pre_sig0(&signer, 0, 300); let sig = signer.sign_message(&question, &pre_sig0).unwrap(); println!("sig: {sig:?}"); assert!(!sig.is_empty()); assert!(sig0key.verify_message(&question, &sig, &pre_sig0).is_ok()); // now test that the sig0 record works correctly. assert!(question.sig0().is_empty()); question.finalize(&signer, 0).expect("should have signed"); assert!(!question.sig0().is_empty()); let sig = signer.sign_message(&question, &pre_sig0); println!("sig after sign: {sig:?}"); if let Some(RData::DNSSEC(DNSSECRData::SIG(ref sig))) = question.sig0()[0].data() { assert!(sig0key.verify_message(&question, sig.sig(), sig).is_ok()); } } #[test] #[allow(deprecated)] fn test_sign_and_verify_rrset() { let rsa = Rsa::generate(2048).unwrap(); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key .to_sig0key_with_usage(Algorithm::RSASHA256, KeyUsage::Zone) .unwrap(); let signer = SigSigner::sig0(sig0key, key, Name::root()); let origin: Name = Name::parse("example.com.", None).unwrap(); let rrsig = Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::RRSIG) .set_dns_class(DNSClass::IN) .set_data(Some(RRSIG::new( RecordType::NS, Algorithm::RSASHA256, origin.num_labels(), 86400, 5, 0, signer.calculate_key_tag().unwrap(), origin.clone(), vec![], ))) .clone(); let rrset = vec![ Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS(NS(Name::parse( "a.iana-servers.net.", None, ) .unwrap())))) .clone(), Record::new() .set_name(origin) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS(NS(Name::parse( "b.iana-servers.net.", None, ) .unwrap())))) .clone(), ]; let tbs = tbs::rrset_tbs_with_rrsig(&rrsig, &rrset).unwrap(); let sig = signer.sign(&tbs).unwrap(); let pub_key = signer.key().to_public_bytes().unwrap(); let pub_key = PublicKeyEnum::from_public_bytes(&pub_key, Algorithm::RSASHA256).unwrap(); assert!(pub_key .verify(Algorithm::RSASHA256, tbs.as_ref(), &sig) .is_ok()); } fn get_rsa_from_vec(params: &[u32]) -> Result, openssl::error::ErrorStack> { Rsa::from_private_components( BigNum::from_u32(params[0]).unwrap(), // modulus: n BigNum::from_u32(params[1]).unwrap(), // public exponent: e, BigNum::from_u32(params[2]).unwrap(), // private exponent: de, BigNum::from_u32(params[3]).unwrap(), // prime1: p, BigNum::from_u32(params[4]).unwrap(), // prime2: q, BigNum::from_u32(params[5]).unwrap(), // exponent1: dp, BigNum::from_u32(params[6]).unwrap(), // exponent2: dq, BigNum::from_u32(params[7]).unwrap(), // coefficient: qi ) } #[test] #[allow(deprecated)] #[allow(clippy::unreadable_literal)] fn test_calculate_key_tag() { let test_vectors = vec![ (vec![33, 3, 21, 11, 3, 1, 1, 1], 9739), ( vec![ 0xc2fedb69, 0x10001, 0x6ebb9209, 0xf743, 0xc9e3, 0xd07f, 0x6275, 0x1095, ], 42354, ), ]; for &(ref input_data, exp_result) in test_vectors.iter() { let rsa = get_rsa_from_vec(input_data).unwrap(); let rsa_pem = rsa.private_key_to_pem().unwrap(); println!("pkey:\n{}", String::from_utf8(rsa_pem).unwrap()); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key .to_sig0key_with_usage(Algorithm::RSASHA256, KeyUsage::Zone) .unwrap(); let signer = SigSigner::sig0(sig0key, key, Name::root()); let key_tag = signer.calculate_key_tag().unwrap(); assert_eq!(key_tag, exp_result); } } #[test] #[allow(deprecated)] fn test_calculate_key_tag_pem() { let x = "-----BEGIN RSA PRIVATE KEY----- MC0CAQACBQC+L6pNAgMBAAECBQCYj0ZNAgMA9CsCAwDHZwICeEUCAnE/AgMA3u0= -----END RSA PRIVATE KEY----- "; let rsa = Rsa::private_key_from_pem(x.as_bytes()).unwrap(); let rsa_pem = rsa.private_key_to_pem().unwrap(); println!("pkey:\n{}", String::from_utf8(rsa_pem).unwrap()); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key .to_sig0key_with_usage(Algorithm::RSASHA256, KeyUsage::Zone) .unwrap(); let signer = SigSigner::sig0(sig0key, key, Name::root()); let key_tag = signer.calculate_key_tag().unwrap(); assert_eq!(key_tag, 28551); } // TODO: these tests technically came from TBS in hickory_proto #[cfg(feature = "openssl")] #[allow(clippy::module_inception)] #[cfg(test)] mod tests { use openssl::rsa::Rsa; use crate::rr::dnssec::rdata::RRSIG; use crate::rr::dnssec::tbs::*; use crate::rr::dnssec::*; use crate::rr::rdata::{CNAME, NS}; use crate::rr::*; #[test] fn test_rrset_tbs() { let rsa = Rsa::generate(2048).unwrap(); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key.to_sig0key(Algorithm::RSASHA256).unwrap(); let signer = SigSigner::sig0(sig0key, key, Name::root()); let origin: Name = Name::parse("example.com.", None).unwrap(); let rrsig = Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::RRSIG) .set_dns_class(DNSClass::IN) .set_data(Some(RRSIG::new( RecordType::NS, Algorithm::RSASHA256, origin.num_labels(), 86400, 5, 0, signer.calculate_key_tag().unwrap(), origin.clone(), vec![], ))) .clone(); let rrset = vec![ Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS(NS(Name::parse( "a.iana-servers.net.", None, ) .unwrap())))) .clone(), Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS(NS(Name::parse( "b.iana-servers.net.", None, ) .unwrap())))) .clone(), ]; let tbs = rrset_tbs_with_rrsig(&rrsig, &rrset).unwrap(); assert!(!tbs.as_ref().is_empty()); let rrset = vec![ Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::CNAME) .set_dns_class(DNSClass::IN) .set_data(Some(RData::CNAME(CNAME( Name::parse("a.iana-servers.net.", None).unwrap(), )))) .clone(), // different type Record::new() .set_name(Name::parse("www.example.com.", None).unwrap()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS(NS(Name::parse( "a.iana-servers.net.", None, ) .unwrap())))) .clone(), // different name Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::CH) .set_data(Some(RData::NS(NS(Name::parse( "a.iana-servers.net.", None, ) .unwrap())))) .clone(), // different class Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS(NS(Name::parse( "a.iana-servers.net.", None, ) .unwrap())))) .clone(), Record::new() .set_name(origin) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS(NS(Name::parse( "b.iana-servers.net.", None, ) .unwrap())))) .clone(), ]; let filtered_tbs = rrset_tbs_with_rrsig(&rrsig, &rrset).unwrap(); assert!(!filtered_tbs.as_ref().is_empty()); assert_eq!(tbs.as_ref(), filtered_tbs.as_ref()); } } } hickory-proto-0.24.0/src/rr/dnssec/supported_algorithm.rs000064400000000000000000000207341046102023000216600ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! bitmap for expressing the set of supported algorithms in edns. use std::fmt::{self, Display, Formatter}; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use tracing::warn; use crate::error::*; use crate::rr::dnssec::Algorithm; use crate::serialize::binary::{BinEncodable, BinEncoder}; /// Used to specify the set of SupportedAlgorithms between a client and server #[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] pub struct SupportedAlgorithms { // right now the number of Algorithms supported are fewer than 16.. bit_map: u8, } impl SupportedAlgorithms { /// Return a new set of Supported algorithms pub fn new() -> Self { Self { bit_map: 0 } } /// Specify the entire set is supported pub fn all() -> Self { Self { bit_map: 0b0111_1111, } } /// Based on the set of Algorithms, return the supported set pub fn from_vec(algorithms: &[Algorithm]) -> Self { let mut supported = Self::new(); for a in algorithms { supported.set(*a); } supported } fn pos(algorithm: Algorithm) -> Option { // not using the values from the RFC's to keep the bit_map space condensed #[allow(deprecated)] let bit_pos: Option = match algorithm { Algorithm::RSASHA1 => Some(0), Algorithm::RSASHA256 => Some(1), Algorithm::RSASHA1NSEC3SHA1 => Some(2), Algorithm::RSASHA512 => Some(3), Algorithm::ECDSAP256SHA256 => Some(4), Algorithm::ECDSAP384SHA384 => Some(5), Algorithm::ED25519 => Some(6), Algorithm::RSAMD5 | Algorithm::DSA | Algorithm::Unknown(_) => None, }; bit_pos.map(|b| 1u8 << b) } fn from_pos(pos: u8) -> Option { // TODO: should build a code generator or possibly a macro for deriving these inversions #[allow(deprecated)] match pos { 0 => Some(Algorithm::RSASHA1), 1 => Some(Algorithm::RSASHA256), 2 => Some(Algorithm::RSASHA1NSEC3SHA1), 3 => Some(Algorithm::RSASHA512), 4 => Some(Algorithm::ECDSAP256SHA256), 5 => Some(Algorithm::ECDSAP384SHA384), 6 => Some(Algorithm::ED25519), _ => None, } } /// Set the specified algorithm as supported pub fn set(&mut self, algorithm: Algorithm) { if let Some(bit_pos) = Self::pos(algorithm) { self.bit_map |= bit_pos; } } /// Returns true if the algorithm is supported pub fn has(self, algorithm: Algorithm) -> bool { if let Some(bit_pos) = Self::pos(algorithm) { (bit_pos & self.bit_map) == bit_pos } else { false } } /// Return an Iterator over the supported set. pub fn iter(&self) -> SupportedAlgorithmsIter<'_> { SupportedAlgorithmsIter::new(self) } /// Return the count of supported algorithms pub fn len(self) -> u16 { // this is pretty much guaranteed to be less that u16::max_value() self.iter().count() as u16 } /// Return true if no SupportedAlgorithms are set, this implies the option is not supported pub fn is_empty(self) -> bool { self.bit_map == 0 } } impl Default for SupportedAlgorithms { fn default() -> Self { Self::new() } } impl Display for SupportedAlgorithms { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { for a in self.iter() { a.fmt(f)?; f.write_str(", ")?; } Ok(()) } } impl<'a> From<&'a [u8]> for SupportedAlgorithms { fn from(values: &'a [u8]) -> Self { let mut supported = Self::new(); for a in values.iter().map(|i| Algorithm::from_u8(*i)) { match a { Algorithm::Unknown(v) => warn!("unrecognized algorithm: {}", v), a => supported.set(a), } } supported } } impl<'a> From<&'a SupportedAlgorithms> for Vec { fn from(value: &'a SupportedAlgorithms) -> Self { let mut bytes = Self::with_capacity(8); // today this is less than 8 for a in value.iter() { bytes.push(a.into()); } bytes.shrink_to_fit(); bytes } } impl From for SupportedAlgorithms { fn from(algorithm: Algorithm) -> Self { Self::from_vec(&[algorithm]) } } pub struct SupportedAlgorithmsIter<'a> { algorithms: &'a SupportedAlgorithms, current: usize, } impl<'a> SupportedAlgorithmsIter<'a> { pub fn new(algorithms: &'a SupportedAlgorithms) -> Self { SupportedAlgorithmsIter { algorithms, current: 0, } } } impl<'a> Iterator for SupportedAlgorithmsIter<'a> { type Item = Algorithm; fn next(&mut self) -> Option { // some quick bounds checking if self.current > u8::max_value() as usize { return None; } while let Some(algorithm) = SupportedAlgorithms::from_pos(self.current as u8) { self.current += 1; if self.algorithms.has(algorithm) { return Some(algorithm); } } None } } impl BinEncodable for SupportedAlgorithms { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { for a in self.iter() { encoder.emit_u8(a.into())?; } Ok(()) } } #[test] #[allow(deprecated)] fn test_has() { let mut supported = SupportedAlgorithms::new(); supported.set(Algorithm::RSASHA1); assert!(supported.has(Algorithm::RSASHA1)); assert!(!supported.has(Algorithm::RSASHA1NSEC3SHA1)); let mut supported = SupportedAlgorithms::new(); supported.set(Algorithm::RSASHA256); assert!(!supported.has(Algorithm::RSASHA1)); assert!(!supported.has(Algorithm::RSASHA1NSEC3SHA1)); assert!(supported.has(Algorithm::RSASHA256)); } #[test] #[allow(deprecated)] fn test_iterator() { let supported = SupportedAlgorithms::all(); assert_eq!(supported.iter().count(), 7); // it just so happens that the iterator has a fixed order... let supported = SupportedAlgorithms::all(); let mut iter = supported.iter(); assert_eq!(iter.next(), Some(Algorithm::RSASHA1)); assert_eq!(iter.next(), Some(Algorithm::RSASHA256)); assert_eq!(iter.next(), Some(Algorithm::RSASHA1NSEC3SHA1)); assert_eq!(iter.next(), Some(Algorithm::RSASHA512)); assert_eq!(iter.next(), Some(Algorithm::ECDSAP256SHA256)); assert_eq!(iter.next(), Some(Algorithm::ECDSAP384SHA384)); assert_eq!(iter.next(), Some(Algorithm::ED25519)); let mut supported = SupportedAlgorithms::new(); supported.set(Algorithm::RSASHA256); supported.set(Algorithm::RSASHA512); let mut iter = supported.iter(); assert_eq!(iter.next(), Some(Algorithm::RSASHA256)); assert_eq!(iter.next(), Some(Algorithm::RSASHA512)); } #[test] #[allow(deprecated)] fn test_vec() { let supported = SupportedAlgorithms::all(); let array: Vec = (&supported).into(); let decoded: SupportedAlgorithms = (&array as &[_]).into(); assert_eq!(supported, decoded); let mut supported = SupportedAlgorithms::new(); supported.set(Algorithm::RSASHA256); supported.set(Algorithm::ECDSAP256SHA256); supported.set(Algorithm::ECDSAP384SHA384); supported.set(Algorithm::ED25519); let array: Vec = (&supported).into(); let decoded: SupportedAlgorithms = (&array as &[_]).into(); assert_eq!(supported, decoded); assert!(!supported.has(Algorithm::RSASHA1)); assert!(!supported.has(Algorithm::RSASHA1NSEC3SHA1)); assert!(supported.has(Algorithm::RSASHA256)); assert!(supported.has(Algorithm::ECDSAP256SHA256)); assert!(supported.has(Algorithm::ECDSAP384SHA384)); assert!(supported.has(Algorithm::ED25519)); } hickory-proto-0.24.0/src/rr/dnssec/tbs.rs000064400000000000000000000246321046102023000163560ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! hash functions for DNSSEC operations use crate::{ error::*, rr::{dnssec::Algorithm, DNSClass, Name, Record, RecordType}, serialize::binary::{BinEncodable, BinEncoder, EncodeMode}, }; use super::rdata::{sig, RRSIG, SIG}; /// Data To Be Signed. pub struct TBS(Vec); impl<'a> From<&'a [u8]> for TBS { fn from(slice: &'a [u8]) -> Self { Self(slice.to_owned()) } } impl AsRef<[u8]> for TBS { fn as_ref(&self) -> &[u8] { self.0.as_ref() } } /// Returns the to-be-signed serialization of the given message. pub fn message_tbs(message: &M, pre_sig0: &SIG) -> ProtoResult { // TODO: should perform the serialization and sign block by block to reduce the max memory // usage, though at 4k max, this is probably unnecessary... For AXFR and large zones, it's // more important let mut buf: Vec = Vec::with_capacity(512); let mut buf2: Vec = Vec::with_capacity(512); { let mut encoder: BinEncoder<'_> = BinEncoder::with_mode(&mut buf, EncodeMode::Normal); assert!(sig::emit_pre_sig( &mut encoder, pre_sig0.type_covered(), pre_sig0.algorithm(), pre_sig0.num_labels(), pre_sig0.original_ttl(), pre_sig0.sig_expiration(), pre_sig0.sig_inception(), pre_sig0.key_tag(), pre_sig0.signer_name(), ) .is_ok()); // need a separate encoder here, as the encoding references absolute positions // inside the buffer. If the buffer already contains the sig0 RDATA, offsets // are wrong and the signature won't match. let mut encoder2: BinEncoder<'_> = BinEncoder::with_mode(&mut buf2, EncodeMode::Signing); message.emit(&mut encoder2).unwrap(); // coding error if this panics (i think?) } buf.append(&mut buf2); Ok(TBS(buf)) } /// Returns the to-be-signed serialization of the given record set. /// /// # Arguments /// /// * `name` - RRset record name /// * `dns_class` - DNSClass, i.e. IN, of the records /// * `num_labels` - number of labels in the name, needed to deal with `*.example.com` /// * `type_covered` - RecordType of the RRSet being hashed /// * `algorithm` - The Algorithm type used for the hashing /// * `original_ttl` - Original TTL is the TTL as specified in the SOA zones RRSet associated record /// * `sig_expiration` - the epoch seconds of when this hashed signature will expire /// * `key_inception` - the epoch seconds of when this hashed signature will be valid /// * `signer_name` - label of the entity responsible for signing this hash /// * `records` - RRSet to hash /// /// # Returns /// /// the binary hash of the specified RRSet and associated information // FIXME: OMG, there are a ton of asserts in here... #[allow(clippy::too_many_arguments)] pub fn rrset_tbs( name: &Name, dns_class: DNSClass, num_labels: u8, type_covered: RecordType, algorithm: Algorithm, original_ttl: u32, sig_expiration: u32, sig_inception: u32, key_tag: u16, signer_name: &Name, records: &[Record], ) -> ProtoResult { // TODO: change this to a BTreeSet so that it's preordered, no sort necessary let mut rrset: Vec<&Record> = Vec::new(); // collect only the records for this rrset for record in records { if dns_class == record.dns_class() && type_covered == record.record_type() && name == record.name() { rrset.push(record); } } // put records in canonical order rrset.sort(); let name = determine_name(name, num_labels)?; // TODO: rather than buffering here, use the Signer/Verifier? might mean fewer allocations... let mut buf: Vec = Vec::new(); { let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut buf); encoder.set_canonical_names(true); // signed_data = RRSIG_RDATA | RR(1) | RR(2)... where // // "|" denotes concatenation // // RRSIG_RDATA is the wire format of the RRSIG RDATA fields // with the Signature field excluded and the Signer's Name // in canonical form. assert!(sig::emit_pre_sig( &mut encoder, type_covered, algorithm, name.num_labels(), original_ttl, sig_expiration, sig_inception, key_tag, signer_name, ) .is_ok()); // construct the rrset signing data for record in rrset { // RR(i) = name | type | class | OrigTTL | RDATA length | RDATA // // name is calculated according to the function in the RFC 4035 assert!(name .to_lowercase() .emit_as_canonical(&mut encoder, true) .is_ok()); // // type is the RRset type and all RRs in the class assert!(type_covered.emit(&mut encoder).is_ok()); // // class is the RRset's class assert!(dns_class.emit(&mut encoder).is_ok()); // // OrigTTL is the value from the RRSIG Original TTL field assert!(encoder.emit_u32(original_ttl).is_ok()); // // RDATA length // TODO: add support to the encoder to set a marker to go back and write the length let mut rdata_buf = Vec::new(); { let mut rdata_encoder = BinEncoder::new(&mut rdata_buf); rdata_encoder.set_canonical_names(true); if let Some(rdata) = record.data() { assert!(rdata.emit(&mut rdata_encoder).is_ok()); } } assert!(encoder.emit_u16(rdata_buf.len() as u16).is_ok()); // // All names in the RDATA field are in canonical form (set above) assert!(encoder.emit_vec(&rdata_buf).is_ok()); } } Ok(TBS(buf)) } /// Returns the to-be-signed serialization of the given record set using the information /// provided from the RRSIG record. /// /// # Arguments /// /// * `rrsig` - SIG or RRSIG record, which was produced from the RRSet /// * `records` - RRSet records to sign with the information in the `rrsig` /// /// # Return /// /// binary hash of the RRSet with the information from the RRSIG record pub fn rrset_tbs_with_rrsig(rrsig: &Record, records: &[Record]) -> ProtoResult { if let Some(sig) = rrsig.data() { rrset_tbs_with_sig(rrsig.name(), rrsig.dns_class(), sig, records) } else { Err(format!("could not determine name from {}", rrsig.name()).into()) } } /// Returns the to-be-signed serialization of the given record set using the information /// provided from the SIG record. /// /// # Arguments /// /// * `name` - labels of the record to sign /// * `dns_class` - DNSClass of the RRSet, i.e. IN /// * `sig` - SIG or RRSIG record, which was produced from the RRSet /// * `records` - RRSet records to sign with the information in the `rrsig` /// /// # Return /// /// binary hash of the RRSet with the information from the RRSIG record pub fn rrset_tbs_with_sig( name: &Name, dns_class: DNSClass, sig: &SIG, records: &[Record], ) -> ProtoResult { rrset_tbs( name, dns_class, sig.num_labels(), sig.type_covered(), sig.algorithm(), sig.original_ttl(), sig.sig_expiration(), sig.sig_inception(), sig.key_tag(), sig.signer_name(), records, ) } /// [RFC 4035](https://tools.ietf.org/html/rfc4035), DNSSEC Protocol Modifications, March 2005 /// /// ```text /// /// 5.3.2. Reconstructing the Signed Data /// ... /// To calculate the name: /// let rrsig_labels = the value of the RRSIG Labels field /// /// let fqdn = RRset's fully qualified domain name in /// canonical form /// /// let fqdn_labels = Label count of the fqdn above. /// /// if rrsig_labels = fqdn_labels, /// name = fqdn /// /// if rrsig_labels < fqdn_labels, /// name = "*." | the rightmost rrsig_label labels of the /// fqdn /// /// if rrsig_labels > fqdn_labels /// the RRSIG RR did not pass the necessary validation /// checks and MUST NOT be used to authenticate this /// RRset. /// /// The canonical forms for names and RRsets are defined in [RFC4034]. /// ``` pub fn determine_name(name: &Name, num_labels: u8) -> Result { // To calculate the name: // let rrsig_labels = the value of the RRSIG Labels field // // let fqdn = RRset's fully qualified domain name in // canonical form // // let fqdn_labels = Label count of the fqdn above. let fqdn_labels = name.num_labels(); // if rrsig_labels = fqdn_labels, // name = fqdn if fqdn_labels == num_labels { return Ok(name.clone()); } // if rrsig_labels < fqdn_labels, // name = "*." | the rightmost rrsig_label labels of the // fqdn if num_labels < fqdn_labels { let mut star_name: Name = Name::from_labels(vec![b"*" as &[u8]]).unwrap(); let rightmost = name.trim_to(num_labels as usize); if !rightmost.is_root() { star_name = star_name.append_name(&rightmost)?; return Ok(star_name); } return Ok(star_name); } // // if rrsig_labels > fqdn_labels // the RRSIG RR did not pass the necessary validation // checks and MUST NOT be used to authenticate this // RRset. Err(format!("could not determine name from {name}").into()) } hickory-proto-0.24.0/src/rr/dnssec/trust_anchor.rs000064400000000000000000000053671046102023000203050ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! Allows for the root trust_anchor to either be added to or replaced for dns_sec validation. use std::default::Default; use crate::rr::dnssec::PublicKey; const ROOT_ANCHOR_ORIG: &[u8] = include_bytes!("roots/19036.rsa"); const ROOT_ANCHOR_2018: &[u8] = include_bytes!("roots/20326.rsa"); /// The root set of trust anchors for validating DNSSEC, anything in this set will be trusted #[derive(Clone)] pub struct TrustAnchor { // TODO: these should also store some information, or more specifically, metadata from the signed // public certificate. pkeys: Vec>, } impl Default for TrustAnchor { fn default() -> Self { Self { pkeys: vec![ROOT_ANCHOR_ORIG.to_owned(), ROOT_ANCHOR_2018.to_owned()], } } } impl TrustAnchor { /// Creates a new empty trust anchor set pub fn new() -> Self { Self { pkeys: vec![] } } /// determines if the key is in the trust anchor set with the raw dnskey bytes /// /// # Arguments /// /// * `other_key` - The raw dnskey in bytes pub fn contains_dnskey_bytes(&self, other_key: &[u8]) -> bool { self.pkeys.iter().any(|k| other_key == k.as_slice()) } /// determines if the key is in the trust anchor set pub fn contains(&self, other_key: &P) -> bool { self.contains_dnskey_bytes(other_key.public_bytes()) } /// inserts the trust_anchor to the trusted chain pub fn insert_trust_anchor(&mut self, public_key: &P) { if !self.contains(public_key) { self.pkeys.push(public_key.public_bytes().to_vec()) } } /// get the trust anchor at the specified index pub fn get(&self, idx: usize) -> &[u8] { &self.pkeys[idx] } /// number of keys in trust_anchor pub fn len(&self) -> usize { self.pkeys.len() } /// returns true if there are no keys in the trust_anchor pub fn is_empty(&self) -> bool { self.pkeys.is_empty() } } #[test] fn test_kjqmt7v() { let trust = TrustAnchor::default(); assert_eq!(trust.get(0), ROOT_ANCHOR_ORIG); assert!(trust.contains_dnskey_bytes(ROOT_ANCHOR_ORIG)); } hickory-proto-0.24.0/src/rr/dnssec/tsig.rs000064400000000000000000000344151046102023000165340ustar 00000000000000// Copyright 2015-2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Trust dns implementation of Secret Key Transaction Authentication for DNS (TSIG) //! [RFC 8945](https://www.rfc-editor.org/rfc/rfc8945) November 2020 //! //! Current deviation from RFC in implementation as of 2022-10-28 //! //! - Mac checking don't support HMAC truncation with TSIG (pedantic constant time verification) //! - Time checking not in TSIG implementation but in caller use std::ops::Range; use std::sync::Arc; use tracing::debug; use crate::error::ProtoErrorKind; use crate::error::{ProtoError, ProtoResult}; use crate::op::{Message, MessageFinalizer, MessageVerifier}; use crate::rr::dnssec::rdata::tsig::{ make_tsig_record, message_tbs, signed_bitmessage_to_buf, TsigAlgorithm, TSIG, }; use crate::rr::dnssec::rdata::DNSSECRData; use crate::rr::{Name, RData, Record}; use crate::xfer::DnsResponse; /// Struct to pass to a client for it to authenticate requests using TSIG. #[derive(Clone)] pub struct TSigner(Arc); struct TSignerInner { key: Vec, // TODO this might want to be some sort of auto-zeroing on drop buffer, as it's cryptographic material algorithm: TsigAlgorithm, signer_name: Name, fudge: u16, } impl TSigner { /// Create a new Tsigner from its parts /// /// # Arguments /// /// * `key` - cryptographic key used to authenticate exchanges /// * `algorithm` - algorithm used to authenticate exchanges /// * `signer_name` - name of the key. Must match the name known to the server /// * `fudge` - maximum difference between client and server time, in seconds, see [fudge](TSigner::fudge) for details pub fn new( key: Vec, algorithm: TsigAlgorithm, signer_name: Name, fudge: u16, ) -> ProtoResult { if algorithm.supported() { Ok(Self(Arc::new(TSignerInner { key, algorithm, signer_name, fudge, }))) } else { Err(ProtoErrorKind::TsigUnsupportedMacAlgorithm(algorithm).into()) } } /// Return the key used for message authentication pub fn key(&self) -> &[u8] { &self.0.key } /// Return the algorithm used for message authentication pub fn algorithm(&self) -> &TsigAlgorithm { &self.0.algorithm } /// Name of the key used by this signer pub fn signer_name(&self) -> &Name { &self.0.signer_name } /// Maximum time difference between client time when issuing a message, and server time when /// receiving it, in second. If time is out, the server will consider the request invalid. /// Longer values means more room for replay by an attacker. A few minutes are usually a good /// value. pub fn fudge(&self) -> u16 { self.0.fudge } /// Compute authentication tag for a buffer pub fn sign(&self, tbs: &[u8]) -> ProtoResult> { self.0.algorithm.mac_data(&self.0.key, tbs) } /// Compute authentication tag for a message pub fn sign_message(&self, message: &Message, pre_tsig: &TSIG) -> ProtoResult> { message_tbs(None, message, pre_tsig, &self.0.signer_name).and_then(|tbs| self.sign(&tbs)) } /// Verify hmac in constant time to prevent timing attacks pub fn verify(&self, tbv: &[u8], tag: &[u8]) -> ProtoResult<()> { self.0.algorithm.verify_mac(&self.0.key, tbv, tag) } /// Verify the message is correctly signed /// This does not perform time verification on its own, instead one should verify current time /// lie in returned Range /// /// # Arguments /// * `previous_hash` - Hash of the last message received before this one, or of the query for /// the first message /// * `message` - byte buffer containing current message /// * `first_message` - is this the first response message /// /// # Returns /// Return Ok(_) on valid signature. Inner tuple contain the following values, in order: /// * a byte buffer containing the hash of this message. Need to be passed back when /// authenticating next message /// * a Range of time that is acceptable /// * the time the signature was emitted. It must be greater or equal to the time of previous /// messages, if any pub fn verify_message_byte( &self, previous_hash: Option<&[u8]>, message: &[u8], first_message: bool, ) -> ProtoResult<(Vec, Range, u64)> { let (tbv, record) = signed_bitmessage_to_buf(previous_hash, message, first_message)?; let tsig = if let Some(RData::DNSSEC(DNSSECRData::TSIG(tsig))) = record.data() { tsig } else { unreachable!("tsig::signed_message_to_buff always returns a TSIG record") }; // https://tools.ietf.org/html/rfc8945#section-5.2 // 1. Check key if record.name() != &self.0.signer_name || tsig.algorithm() != &self.0.algorithm { return Err(ProtoErrorKind::TsigWrongKey.into()); } // 2. Check MAC // note: that this verification does not allow for truncation of the HMAC, which technically the RFC suggests. // this is to be pedantic about constant time HMAC validation (prevent timing attacks) as well as any security // concerns about MAC truncation and collisions. if tsig.mac().len() < tsig.algorithm().output_len()? { return Err(ProtoError::from("Please file an issue with https://github.com/hickory-dns/hickory-dns to support truncated HMACs with TSIG")); } // verify the MAC let mac = tsig.mac(); self.verify(&tbv, mac) .map_err(|_e| ProtoError::from("tsig validation error: invalid signature"))?; // 3. Check time values // we don't actually have time here so we will let upper level decide // this is technically in violation of the RFC, in case both time and // truncation policy are bad, time should be reported and this code will report // truncation issue instead // 4. Check truncation policy // see not above in regards to not supporting verification of truncated HMACs. // if tsig.mac().len() < std::cmp::max(10, self.0.algorithm.output_len()? / 2) { // return Err(ProtoError::from( // "tsig validation error: truncated signature", // )); // } Ok(( tsig.mac().to_vec(), Range { start: tsig.time() - tsig.fudge() as u64, end: tsig.time() + tsig.fudge() as u64, }, tsig.time(), )) } } impl MessageFinalizer for TSigner { fn finalize_message( &self, message: &Message, current_time: u32, ) -> ProtoResult<(Vec, Option)> { debug!("signing message: {:?}", message); let current_time = current_time as u64; let pre_tsig = TSIG::new( self.0.algorithm.clone(), current_time, self.0.fudge, Vec::new(), message.id(), 0, Vec::new(), ); let mut signature: Vec = self.sign_message(message, &pre_tsig)?; let tsig = make_tsig_record( self.0.signer_name.clone(), pre_tsig.set_mac(signature.clone()), ); let self2 = self.clone(); let mut remote_time = 0; let verifier = move |dns_response: &[u8]| { let (last_sig, range, rt) = self2.verify_message_byte( Some(signature.as_ref()), dns_response, remote_time == 0, )?; if rt >= remote_time && range.contains(¤t_time) // this assumes a no-latency answer { signature = last_sig; remote_time = rt; Ok(DnsResponse::new( Message::from_vec(dns_response)?, dns_response.to_vec(), )) } else { Err(ProtoError::from("tsig validation error: outdated response")) } }; Ok((vec![tsig], Some(Box::new(verifier)))) } } #[cfg(test)] #[cfg(any(feature = "dnssec-ring", feature = "dnssec-openssl"))] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use crate::op::{Message, Query}; use crate::rr::Name; use crate::serialize::binary::BinEncodable; use super::*; fn assert_send_and_sync() {} #[test] fn test_send_and_sync() { assert_send_and_sync::(); } #[test] fn test_sign_and_verify_message_tsig() { let time_begin = 1609459200u64; let fudge = 300u64; let origin: Name = Name::parse("example.com.", None).unwrap(); let key_name: Name = Name::from_ascii("key_name").unwrap(); let mut question: Message = Message::new(); let mut query: Query = Query::new(); query.set_name(origin); question.add_query(query); let sig_key = b"some_key".to_vec(); let signer = TSigner::new(sig_key, TsigAlgorithm::HmacSha512, key_name, fudge as u16).unwrap(); assert!(question.signature().is_empty()); question .finalize(&signer, time_begin as u32) .expect("should have signed"); assert!(!question.signature().is_empty()); let (_, validity_range, _) = signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .unwrap(); assert!(validity_range.contains(&(time_begin + fudge / 2))); // slightly outdated, but still to be acceptable assert!(validity_range.contains(&(time_begin - fudge / 2))); // sooner than our time, but still acceptable assert!(!validity_range.contains(&(time_begin + fudge * 2))); // too late to be accepted assert!(!validity_range.contains(&(time_begin - fudge * 2))); // too soon to be accepted } // make rejection tests shorter by centralizing common setup code fn get_message_and_signer() -> (Message, TSigner) { let time_begin = 1609459200u64; let fudge = 300u64; let origin: Name = Name::parse("example.com.", None).unwrap(); let key_name: Name = Name::from_ascii("key_name").unwrap(); let mut question: Message = Message::new(); let mut query: Query = Query::new(); query.set_name(origin); question.add_query(query); let sig_key = b"some_key".to_vec(); let signer = TSigner::new(sig_key, TsigAlgorithm::HmacSha512, key_name, fudge as u16).unwrap(); assert!(question.signature().is_empty()); question .finalize(&signer, time_begin as u32) .expect("should have signed"); assert!(!question.signature().is_empty()); // this should be ok, it has not been tampered with assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_ok()); (question, signer) } #[test] fn test_sign_and_verify_message_tsig_reject_keyname() { let (mut question, signer) = get_message_and_signer(); let other_name: Name = Name::from_ascii("other_name").unwrap(); let mut signature = question.take_signature().remove(0); signature.set_name(other_name); question.add_tsig(signature); assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_err()); } #[test] fn test_sign_and_verify_message_tsig_reject_invalid_mac() { let (mut question, signer) = get_message_and_signer(); let mut query: Query = Query::new(); let origin: Name = Name::parse("example.net.", None).unwrap(); query.set_name(origin); question.add_query(query); assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_err()); } #[test] #[cfg(feature = "hmac_truncation")] // not currently supported for security reasons fn test_sign_and_verify_message_tsig_truncation() { let (mut question, signer) = get_message_and_signer(); { let mut signature = question.take_signature().remove(0); if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() { let mut mac = tsig.mac().to_vec(); mac.push(0); // make one longer than sha512 std::mem::swap(tsig, &mut tsig.clone().set_mac(mac)); } else { panic!("should have been a TSIG"); } question.add_tsig(signature); } // we are longer, there is a problem assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_err()); { let mut signature = question.take_signature().remove(0); if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() { // sha512 is 512 bits, half of that is 256 bits, /8 for byte let mac = tsig.mac()[..256 / 8].to_vec(); std::mem::swap(tsig, &mut tsig.clone().set_mac(mac)); } else { panic!("should have been a TSIG"); } question.add_tsig(signature); } // we are at half, it's allowed assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_ok()); { let mut signature = question.take_signature().remove(0); if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() { // less than half of sha512 let mac = tsig.mac()[..240 / 8].to_vec(); std::mem::swap(tsig, &mut tsig.clone().set_mac(mac)); } else { panic!("should have been a TSIG"); } question.add_tsig(signature); } assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_err()); } } hickory-proto-0.24.0/src/rr/dnssec/verifier.rs000064400000000000000000000063101046102023000173720ustar 00000000000000// Copyright 2015-2023 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Verifier is a structure for performing many of the signing processes of the DNSSEC specification use crate::{ error::ProtoResult, rr::{ dnssec::{ rdata::{DNSKEY, KEY, RRSIG, SIG}, tbs, Algorithm, PublicKey, PublicKeyEnum, }, DNSClass, Name, Record, }, serialize::binary::BinEncodable, }; /// Types which are able to verify DNS based signatures pub trait Verifier { /// Return the algorithm which this Verifier covers fn algorithm(&self) -> Algorithm; /// Return the public key associated with this verifier fn key(&self) -> ProtoResult>; /// Verifies the hash matches the signature with the current `key`. /// /// # Arguments /// /// * `hash` - the hash to be validated, see `rrset_tbs` /// * `signature` - the signature to use to verify the hash, extracted from an `RData::RRSIG` /// for example. /// /// # Return value /// /// True if and only if the signature is valid for the hash. /// false if the `key`. fn verify(&self, hash: &[u8], signature: &[u8]) -> ProtoResult<()> { self.key()?.verify(self.algorithm(), hash, signature) } /// Verifies a message with the against the given signature, i.e. SIG0 /// /// # Arguments /// /// * `message` - the message to verify /// * `signature` - the signature to use for validation /// /// # Return value /// /// `true` if the message could be validated against the signature, `false` otherwise fn verify_message( &self, message: &M, signature: &[u8], sig0: &SIG, ) -> ProtoResult<()> { tbs::message_tbs(message, sig0).and_then(|tbs| self.verify(tbs.as_ref(), signature)) } /// Verifies an RRSig with the associated key, e.g. DNSKEY /// /// # Arguments /// /// * `name` - name associated with the rrsig being validated /// * `dns_class` - DNSClass of the records, generally IN /// * `sig` - signature record being validated /// * `records` - Records covered by SIG fn verify_rrsig( &self, name: &Name, dns_class: DNSClass, sig: &RRSIG, records: &[Record], ) -> ProtoResult<()> { let rrset_tbs = tbs::rrset_tbs_with_sig(name, dns_class, sig, records)?; self.verify(rrset_tbs.as_ref(), sig.sig()) } } impl Verifier for DNSKEY { fn algorithm(&self) -> Algorithm { self.algorithm() } fn key(&self) -> ProtoResult> { PublicKeyEnum::from_public_bytes(self.public_key(), self.algorithm()) } } impl Verifier for KEY { fn algorithm(&self) -> Algorithm { self.algorithm() } fn key(&self) -> ProtoResult> { PublicKeyEnum::from_public_bytes(self.public_key(), self.algorithm()) } } hickory-proto-0.24.0/src/rr/domain/label.rs000064400000000000000000000435631046102023000166410ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Labels are used as the internal components of a Name. //! //! A label is stored internally as ascii, where all unicode characters are converted to punycode internally. #[allow(clippy::useless_attribute)] #[allow(unused)] #[allow(deprecated)] use std::ascii::AsciiExt; use std::borrow::Borrow; use std::cmp::{Ordering, PartialEq}; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::hash::{Hash, Hasher}; use tinyvec::TinyVec; use idna; use tracing::debug; use crate::error::*; const WILDCARD: &[u8] = b"*"; const IDNA_PREFIX: &[u8] = b"xn--"; /// Labels are always stored as ASCII, unicode characters must be encoded with punycode #[derive(Clone, Eq)] pub struct Label(TinyVec<[u8; 24]>); impl Label { /// These must only be ASCII, with unicode encoded to PunyCode, or other such transformation. /// /// This uses the bytes as raw ascii values, with nothing escaped on the wire. /// Generally users should use `from_str` or `from_ascii` pub fn from_raw_bytes(bytes: &[u8]) -> ProtoResult { // Check for label validity. // RFC 2181, Section 11 "Name Syntax". // > The length of any one label is limited to between 1 and 63 octets. if bytes.is_empty() { return Err("Label requires a minimum length of 1".into()); } if bytes.len() > 63 { return Err(ProtoErrorKind::LabelBytesTooLong(bytes.len()).into()); }; Ok(Self(TinyVec::from(bytes))) } /// Translates this string into IDNA safe name, encoding to punycode as necessary. pub fn from_utf8(s: &str) -> ProtoResult { if s.as_bytes() == WILDCARD { return Ok(Self::wildcard()); } // special case for SRV type records if s.starts_with('_') { return Self::from_ascii(s); } match idna::Config::default() .use_std3_ascii_rules(true) .transitional_processing(true) // length don't exceding 63 is done in `from_ascii` // on puny encoded string // idna error are opaque so early failure is not possible. .verify_dns_length(false) .to_ascii(s) { Ok(puny) => Self::from_ascii(&puny), e => Err(format!("Label contains invalid characters: {e:?}").into()), } } /// Takes the ascii string and returns a new label. /// /// This will return an Error if the label is not an ascii string pub fn from_ascii(s: &str) -> ProtoResult { if s.len() > 63 { return Err(ProtoErrorKind::LabelBytesTooLong(s.len()).into()); } if s.as_bytes() == WILDCARD { return Ok(Self::wildcard()); } if !s.is_empty() && s.is_ascii() && s.chars().take(1).all(|c| is_safe_ascii(c, true, false)) && s.chars().skip(1).all(|c| is_safe_ascii(c, false, false)) { Self::from_raw_bytes(s.as_bytes()) } else { Err(format!("Malformed label: {s}").into()) } } /// Returns a new Label of the Wildcard, i.e. "*" pub fn wildcard() -> Self { Self(TinyVec::from(WILDCARD)) } /// Converts this label to lowercase pub fn to_lowercase(&self) -> Self { // TODO: replace case conversion when (ascii_ctype #39658) stabilizes if let Some((idx, _)) = self .0 .iter() .enumerate() .find(|&(_, c)| *c != c.to_ascii_lowercase()) { let mut lower_label: Vec = self.0.to_vec(); lower_label[idx..].make_ascii_lowercase(); Self(TinyVec::from(lower_label.as_slice())) } else { self.clone() } } /// Returns true if this label is the wildcard, '*', label pub fn is_wildcard(&self) -> bool { self.as_bytes() == WILDCARD } /// Returns the lenght in bytes of this label pub fn len(&self) -> usize { self.0.len() } /// True if the label contains no characters pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Returns the raw bytes of the label, this is good for writing to the wire. /// /// See [`Display`] for presentation version (unescaped from punycode, etc) pub fn as_bytes(&self) -> &[u8] { &self.0 } /// Performs the equivalence operation disregarding case pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { self.0.eq_ignore_ascii_case(&other.0) } /// compares with the other label, ignoring case pub fn cmp_with_f(&self, other: &Self) -> Ordering { let s = self.0.iter(); let o = other.0.iter(); for (s, o) in s.zip(o) { match F::cmp_u8(*s, *o) { Ordering::Equal => continue, not_eq => return not_eq, } } self.0.len().cmp(&other.0.len()) } /// Performs the conversion to utf8 from IDNA as necessary, see `fmt` for more details pub fn to_utf8(&self) -> String { format!("{self}") } /// Converts this label to safe ascii, escaping characters as necessary /// /// If this is an IDNA, punycode, label, then the xn-- prefix will be maintained as ascii pub fn to_ascii(&self) -> String { let mut ascii = String::with_capacity(self.as_bytes().len()); self.write_ascii(&mut ascii) .expect("should never fail to write a new string"); ascii } /// Writes this label to safe ascii, escaping characters as necessary pub fn write_ascii(&self, f: &mut W) -> Result<(), fmt::Error> { // We can't guarantee that the same input will always translate to the same output fn escape_non_ascii( byte: u8, f: &mut W, is_first: bool, ) -> Result<(), fmt::Error> { let to_triple_escape = |ch: u8| format!("\\{ch:03o}"); let to_single_escape = |ch: char| format!("\\{ch}"); match char::from(byte) { c if is_safe_ascii(c, is_first, true) => f.write_char(c)?, // it's not a control and is printable as well as inside the standard ascii range c if byte > b'\x20' && byte < b'\x7f' => f.write_str(&to_single_escape(c))?, _ => f.write_str(&to_triple_escape(byte))?, } Ok(()) } // traditional ascii case... let mut chars = self.as_bytes().iter(); if let Some(ch) = chars.next() { escape_non_ascii(*ch, f, true)?; } for ch in chars { escape_non_ascii(*ch, f, false)?; } Ok(()) } } impl AsRef<[u8]> for Label { fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl Borrow<[u8]> for Label { fn borrow(&self) -> &[u8] { &self.0 } } fn is_safe_ascii(c: char, is_first: bool, for_encoding: bool) -> bool { match c { c if !c.is_ascii() => false, c if c.is_alphanumeric() => true, '-' if !is_first => true, // dash is allowed '_' => true, // SRV like labels '*' if is_first => true, // wildcard '.' if !for_encoding => true, // needed to allow dots, for things like email addresses _ => false, } } impl Display for Label { /// outputs characters in a safe string manner. /// /// if the string is punycode, i.e. starts with `xn--`, otherwise it translates to a safe ascii string /// escaping characters as necessary. fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { if self.as_bytes().starts_with(IDNA_PREFIX) { // this should never be outside the ascii codes... let label = String::from_utf8_lossy(self.borrow()); let (label, e) = idna::Config::default() .use_std3_ascii_rules(false) .transitional_processing(false) .verify_dns_length(false) .to_unicode(&label); if e.is_ok() { return f.write_str(&label); } else { debug!( "xn-- prefixed string did not translate via IDNA properly: {:?}", e ) } } // it wasn't known to be utf8 self.write_ascii(f) } } impl Debug for Label { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { let label = String::from_utf8_lossy(self.borrow()); f.write_str(&label) } } impl PartialEq for Label { fn eq(&self, other: &Self) -> bool { self.eq_ignore_ascii_case(other) } } impl PartialOrd for Label { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Label { fn cmp(&self, other: &Self) -> Ordering { self.cmp_with_f::(other) } } impl Hash for Label { fn hash(&self, state: &mut H) where H: Hasher, { for b in self.borrow() as &[u8] { state.write_u8(b.to_ascii_lowercase()); } } } /// Label comparison trait for case sensitive or insensitive comparisons pub trait LabelCmp { /// this should mimic the cmp method from [`PartialOrd`] fn cmp_u8(l: u8, r: u8) -> Ordering; } /// For case sensitive comparisons pub(super) struct CaseSensitive; impl LabelCmp for CaseSensitive { fn cmp_u8(l: u8, r: u8) -> Ordering { l.cmp(&r) } } /// For case insensitive comparisons pub(super) struct CaseInsensitive; impl LabelCmp for CaseInsensitive { fn cmp_u8(l: u8, r: u8) -> Ordering { l.to_ascii_lowercase().cmp(&r.to_ascii_lowercase()) } } /// Conversion into a Label pub trait IntoLabel: Sized { /// Convert this into Label fn into_label(self) -> ProtoResult