trust-dns-proto-0.22.0/.cargo_vcs_info.json0000644000000001520000000000100142170ustar { "git": { "sha1": "19b4dc40c046b8d49991bd7b5969333771774f1b" }, "path_in_vcs": "crates/proto" }trust-dns-proto-0.22.0/Cargo.toml0000644000000116000000000000100122150ustar # 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 = "2018" name = "trust-dns-proto" version = "0.22.0" authors = ["Benjamin Fry "] description = """ Trust-DNS is a safe and secure DNS library. This is the foundational DNS protocol library for all Trust-DNS projects. """ homepage = "http://www.trust-dns.org/index.html" documentation = "https://docs.rs/trust-dns-proto" readme = "README.md" keywords = [ "DNS", "BIND", "dig", "named", "dnssec", ] categories = ["network-programming"] license = "MIT/Apache-2.0" repository = "https://github.com/bluejekyll/trust-dns" [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", ] [lib] name = "trust_dns_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.5" [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.http] version = "0.2" optional = true [dependencies.idna] version = "0.2.3" [dependencies.ipnet] version = "2.3.0" [dependencies.js-sys] version = "0.3.44" optional = true [dependencies.lazy_static] version = "1.2.0" [dependencies.native-tls] version = "0.2" optional = true [dependencies.openssl] version = "0.10" features = [ "v102", "v110", ] optional = true [dependencies.quinn] version = "0.8.2" optional = true [dependencies.rand] version = "0.8" [dependencies.ring] version = "0.16" features = ["std"] optional = true [dependencies.rustls] version = "0.20.0" optional = true [dependencies.rustls-pemfile] version = "1.0.0" optional = true [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dependencies.smallvec] version = "1.6" [dependencies.socket2] version = "0.4.0" optional = true [dependencies.thiserror] version = "1.0.20" [dependencies.tinyvec] version = "1.1.1" features = ["alloc"] [dependencies.tokio] version = "1.0" 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.23.0" features = ["early-data"] optional = true [dependencies.tracing] version = "0.1.30" [dependencies.url] version = "2.1.0" [dependencies.wasm-bindgen-crate] version = "0.2.58" optional = true package = "wasm-bindgen" [dependencies.webpki] version = "0.22.0" optional = true [dependencies.webpki-roots] version = "0.22.1" optional = true [dev-dependencies.futures-executor] version = "0.3.5" features = ["std"] default-features = false [dev-dependencies.openssl] version = "0.10" features = [ "v102", "v110", ] [dev-dependencies.tokio] version = "1.0" features = [ "rt", "time", "macros", ] [dev-dependencies.tracing-subscriber] version = "0.3" features = [ "std", "fmt", "env-filter", ] [features] default = ["tokio-runtime"] dns-over-https = [ "bytes", "dns-over-tls", "h2", "http", ] dns-over-https-rustls = [ "dns-over-https", "dns-over-rustls", "webpki-roots", ] dns-over-native-tls = [ "dns-over-tls", "native-tls", "tokio-native-tls", ] dns-over-openssl = [ "dns-over-tls", "openssl", "tokio-openssl", ] dns-over-quic = [ "quinn", "rustls/quic", "dns-over-rustls", "bytes", "webpki-roots", ] dns-over-rustls = [ "dns-over-tls", "rustls", "rustls-pemfile", "tokio-rustls", "webpki", ] dns-over-tls = [] dnssec = [] dnssec-openssl = [ "dnssec", "openssl", ] dnssec-ring = [ "dnssec", "ring", ] mdns = ["socket2/all"] serde-config = [ "serde", "url/serde", ] testing = [] tokio-runtime = [ "tokio/net", "tokio/rt", "tokio/time", "tokio/rt-multi-thread", ] wasm-bindgen = [ "wasm-bindgen-crate", "js-sys", ] [badges.codecov] branch = "main" repository = "bluejekyll/trust-dns" service = "github" [badges.maintenance] status = "actively-developed" trust-dns-proto-0.22.0/Cargo.toml.orig0000644000000111020000000000100131510ustar [package] name = "trust-dns-proto" version = "0.22.0" edition = "2018" authors = ["Benjamin Fry "] # A short blurb about the package. This is not rendered in any format when # uploaded to crates.io (aka this is not markdown) description = """ Trust-DNS is a safe and secure DNS library. This is the foundational DNS protocol library for all Trust-DNS projects. """ # These URLs point to more information about the repository documentation = "https://docs.rs/trust-dns-proto" homepage = "http://www.trust-dns.org/index.html" repository = "https://github.com/bluejekyll/trust-dns" # 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" # This is a small list of keywords used to categorize and search for this # package. keywords = ["DNS", "BIND", "dig", "named", "dnssec"] categories = ["network-programming"] # This is a string description of the license for this package. Currently # crates.io will validate the license provided against a whitelist of known # license identifiers from http://spdx.org/licenses/. Multiple licenses can # be separated with a `/` license = "MIT/Apache-2.0" [badges] #github-actions = { repository = "bluejekyll/trust-dns", branch = "main", workflow = "test" } codecov = { repository = "bluejekyll/trust-dns", branch = "main", service = "github" } maintenance = { status = "actively-developed" } [features] dns-over-tls = [] dns-over-rustls = ["dns-over-tls", "rustls", "rustls-pemfile", "tokio-rustls", "webpki"] dns-over-native-tls = ["dns-over-tls", "native-tls", "tokio-native-tls"] dns-over-openssl = ["dns-over-tls", "openssl", "tokio-openssl"] dns-over-https-rustls = ["dns-over-https", "dns-over-rustls", "webpki-roots"] dns-over-https = ["bytes", "dns-over-tls", "h2", "http"] dns-over-quic = ["quinn", "rustls/quic", "dns-over-rustls", "bytes", "webpki-roots"] dnssec-openssl = ["dnssec", "openssl"] dnssec-ring = ["dnssec", "ring"] dnssec = [] testing = [] 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"] [lib] name = "trust_dns_proto" path = "src/lib.rs" [dependencies] async-trait = "0.1.43" backtrace = { version = "0.3.50", optional = true } bytes = { version = "1", optional = true } cfg-if = "1" data-encoding = "2.2.0" enum-as-inner = "0.5" futures-channel = { version = "0.3.5", default-features = false, features = ["std"] } futures-io = { version = "0.3.5", default-features = false, features = ["std"] } futures-util = { version = "0.3.5", default-features = false, features = ["std"] } h2 = { version = "0.3.0", features = ["stream"], optional = true } http = { version = "0.2", optional = true } idna = "0.2.3" ipnet = "2.3.0" js-sys = { version = "0.3.44", optional = true } lazy_static = "1.2.0" native-tls = { version = "0.2", optional = true } openssl = { version = "0.10", features = ["v102", "v110"], optional = true } quinn = { version = "0.8.2", optional = true } rand = "0.8" ring = { version = "0.16", optional = true, features = ["std"] } rustls = { version = "0.20.0", optional = true } rustls-pemfile = { version = "1.0.0", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } smallvec = "1.6" socket2 = { version = "0.4.0", optional = true } thiserror = "1.0.20" tinyvec = { version = "1.1.1", features = ["alloc"] } tracing = "0.1.30" tokio = { version = "1.0", features = ["io-util"], optional = true } tokio-native-tls = { version = "0.3.0", optional = true } tokio-openssl = { version = "0.6.0", optional = true } tokio-rustls = { version = "0.23.0", optional = true, features = ["early-data"] } url = "2.1.0" wasm-bindgen-crate = { version = "0.2.58", optional = true, package = "wasm-bindgen" } webpki = { version = "0.22.0", optional = true } webpki-roots = { version = "0.22.1", optional = true } [dev-dependencies] futures-executor = { version = "0.3.5", default-features = false, features = ["std"] } openssl = { version = "0.10", features = ["v102", "v110"] } tokio = { version = "1.0", features = ["rt", "time", "macros"] } tracing-subscriber = { version = "0.3", 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"] trust-dns-proto-0.22.0/Cargo.toml.orig000064400000000000000000000111021046102023000156730ustar 00000000000000[package] name = "trust-dns-proto" version = "0.22.0" edition = "2018" authors = ["Benjamin Fry "] # A short blurb about the package. This is not rendered in any format when # uploaded to crates.io (aka this is not markdown) description = """ Trust-DNS is a safe and secure DNS library. This is the foundational DNS protocol library for all Trust-DNS projects. """ # These URLs point to more information about the repository documentation = "https://docs.rs/trust-dns-proto" homepage = "http://www.trust-dns.org/index.html" repository = "https://github.com/bluejekyll/trust-dns" # 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" # This is a small list of keywords used to categorize and search for this # package. keywords = ["DNS", "BIND", "dig", "named", "dnssec"] categories = ["network-programming"] # This is a string description of the license for this package. Currently # crates.io will validate the license provided against a whitelist of known # license identifiers from http://spdx.org/licenses/. Multiple licenses can # be separated with a `/` license = "MIT/Apache-2.0" [badges] #github-actions = { repository = "bluejekyll/trust-dns", branch = "main", workflow = "test" } codecov = { repository = "bluejekyll/trust-dns", branch = "main", service = "github" } maintenance = { status = "actively-developed" } [features] dns-over-tls = [] dns-over-rustls = ["dns-over-tls", "rustls", "rustls-pemfile", "tokio-rustls", "webpki"] dns-over-native-tls = ["dns-over-tls", "native-tls", "tokio-native-tls"] dns-over-openssl = ["dns-over-tls", "openssl", "tokio-openssl"] dns-over-https-rustls = ["dns-over-https", "dns-over-rustls", "webpki-roots"] dns-over-https = ["bytes", "dns-over-tls", "h2", "http"] dns-over-quic = ["quinn", "rustls/quic", "dns-over-rustls", "bytes", "webpki-roots"] dnssec-openssl = ["dnssec", "openssl"] dnssec-ring = ["dnssec", "ring"] dnssec = [] testing = [] 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"] [lib] name = "trust_dns_proto" path = "src/lib.rs" [dependencies] async-trait = "0.1.43" backtrace = { version = "0.3.50", optional = true } bytes = { version = "1", optional = true } cfg-if = "1" data-encoding = "2.2.0" enum-as-inner = "0.5" futures-channel = { version = "0.3.5", default-features = false, features = ["std"] } futures-io = { version = "0.3.5", default-features = false, features = ["std"] } futures-util = { version = "0.3.5", default-features = false, features = ["std"] } h2 = { version = "0.3.0", features = ["stream"], optional = true } http = { version = "0.2", optional = true } idna = "0.2.3" ipnet = "2.3.0" js-sys = { version = "0.3.44", optional = true } lazy_static = "1.2.0" native-tls = { version = "0.2", optional = true } openssl = { version = "0.10", features = ["v102", "v110"], optional = true } quinn = { version = "0.8.2", optional = true } rand = "0.8" ring = { version = "0.16", optional = true, features = ["std"] } rustls = { version = "0.20.0", optional = true } rustls-pemfile = { version = "1.0.0", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } smallvec = "1.6" socket2 = { version = "0.4.0", optional = true } thiserror = "1.0.20" tinyvec = { version = "1.1.1", features = ["alloc"] } tracing = "0.1.30" tokio = { version = "1.0", features = ["io-util"], optional = true } tokio-native-tls = { version = "0.3.0", optional = true } tokio-openssl = { version = "0.6.0", optional = true } tokio-rustls = { version = "0.23.0", optional = true, features = ["early-data"] } url = "2.1.0" wasm-bindgen-crate = { version = "0.2.58", optional = true, package = "wasm-bindgen" } webpki = { version = "0.22.0", optional = true } webpki-roots = { version = "0.22.1", optional = true } [dev-dependencies] futures-executor = { version = "0.3.5", default-features = false, features = ["std"] } openssl = { version = "0.10", features = ["v102", "v110"] } tokio = { version = "1.0", features = ["rt", "time", "macros"] } tracing-subscriber = { version = "0.3", 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"] trust-dns-proto-0.22.0/LICENSE-APACHE000064400000000000000000000261361046102023000147450ustar 00000000000000 Apache License Version 2.0, January 2004 http://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 http://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. trust-dns-proto-0.22.0/LICENSE-MIT000064400000000000000000000021131046102023000144420ustar 00000000000000Copyright (c) 2015 The trust-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. trust-dns-proto-0.22.0/README.md000064400000000000000000000022611046102023000142710ustar 00000000000000# Overview Trust-DNS Proto is the foundational DNS protocol library and implementation for Trust-DNS. Unless you want to manipulate the DNS packets directly, it is likely not the library you want. Please see Trust-DNS [Resolver](https://crates.io/crates/trust-dns-resolver), [Client](https://crates.io/crates/trust-dns-client), or [Server](https://crates.io/crates/trust-dns-server) for higher level interfaces. ## Minimum Rust Version The current minimum rustc version for this project is `1.59` ## Versioning Trust-DNS does it's best job to follow semver. Trust-DNS will be promoted to 1.0 upon stabilization of the publicly exposed APIs. This does not mean that Trust-DNS will necessarily break on upgrades between 0.x updates. Whenever possible, old APIs will be deprecated with notes on what replaced those deprecations. Trust-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. trust-dns-proto-0.22.0/src/error.rs000064400000000000000000000420531046102023000153030ustar 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(not(feature = "openssl"))] use self::not_openssl::SslErrorStack; #[cfg(not(feature = "ring"))] use self::not_ring::Unspecified; #[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 lazy_static::lazy_static; #[cfg(feature = "openssl")] use openssl::error::ErrorStack as SslErrorStack; #[cfg(feature = "ring")] use ring::error::Unspecified; use thiserror::Error; use crate::op::Header; use crate::rr::{Name, RecordType}; use crate::serialize::binary::DecodeError; #[cfg(feature = "backtrace")] #[cfg_attr(docsrs, doc(cfg(feature = "backtrace")))] lazy_static! { /// Boolean for checking if backtrace is enabled at runtime pub static ref ENABLE_BACKTRACE: bool = { 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, /// 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 occured #[cfg(feature = "quinn")] #[error("error creating quic connection: {0}")] QuinnConnect(#[from] quinn::ConnectError), /// A Quinn (QUIC) connection error occured #[cfg(feature = "quinn")] #[error("error with quic connection: {0}")] QuinnConnection(#[from] quinn::ConnectionError), /// A Quinn (QUIC) write error occured #[cfg(feature = "quinn")] #[error("error writing to quic connection: {0}")] QuinnWriteError(#[from] quinn::WriteError), /// A Quinn (QUIC) read error occured #[cfg(feature = "quinn")] #[error("error writing to quic read: {0}")] QuinnReadError(#[from] quinn::ReadExactError), /// A Quinn (QUIC) configuration error occured #[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 occured #[cfg(feature = "rustls")] #[error("rustls construction error: {0}")] RustlsError(#[from] rustls::Error), } /// 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) } } 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() } } /// Stubs for running without OpenSSL #[cfg(not(feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "openssl"))))] pub mod not_openssl { use std; /// SslErrorStac stub #[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" } } } /// Types used without ring #[cfg(not(feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "ring"))))] pub mod not_ring { use std; /// The Unspecified error replacement #[derive(Clone, Copy, Debug)] pub struct Unspecified; 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" } } } 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, 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()), } } } /// 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 {} trust-dns-proto-0.22.0/src/https/error.rs000064400000000000000000000063051046102023000164450ustar 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 h2; 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}")] H2(#[from] h2::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() } } impl From for Error { fn from(msg: h2::Error) -> Self { ErrorKind::H2(msg).into() } } impl From for io::Error { fn from(err: Error) -> Self { Self::new(io::ErrorKind::Other, format!("https: {}", err)) } } trust-dns-proto-0.22.0/src/https/https_client_stream.rs000064400000000000000000000573541046102023000214010ustar 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::convert::TryInto; 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::iocompat::AsyncIoStdAsTokio; use crate::tcp::Connect; use crate::xfer::{DnsRequest, DnsRequestSender, DnsResponse, DnsResponseStream, SerialMessage}; 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, name_server: SocketAddr, ) -> 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::https::request::new(&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::https::MIME_APPLICATION_DNS))?; if content_type != crate::https::MIME_APPLICATION_DNS { return Err(ProtoError::from(format!( "ContentType unsupported (must be '{}'): '{}'", crate::https::MIME_APPLICATION_DNS, content_type ))); } } }; // and finally convert the bytes into a DNS message let message = SerialMessage::new(response_bytes.to_vec(), name_server).to_message()?; Ok(message.into()) } } 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), self.name_server, )) .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), }; HttpsClientConnect::(HttpsClientConnectState::ConnectTcp { name_server, bind_addr: self.bind_addr, tls: Some(tls), }) } } /// A future that resolves to an HttpsClientStream pub struct HttpsClientConnect(HttpsClientConnectState) where S: Connect; impl Future for HttpsClientConnect where S: Connect, { 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: Connect, { ConnectTcp { name_server: SocketAddr, bind_addr: Option, tls: Option, }, 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: Connect, { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { let next = match *self { Self::ConnectTcp { name_server, bind_addr, ref mut tls, } => { debug!("tcp connecting to: {}", name_server); let connect = S::connect_with_bind(name_server, bind_addr); Self::TcpConnecting { connect, name_server, tls: tls.take(), } } 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(test)] mod tests { use std::net::{Ipv4Addr, Ipv6Addr, 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::{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_webpki_roots(); 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, &Ipv4Addr::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, &Ipv6Addr::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_webpki_roots(); 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, &Ipv4Addr::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, &Ipv6Addr::new(0x2606, 0x2800, 0x0220, 0x0001, 0x0248, 0x1893, 0x25c8, 0x1946) ); } fn client_config_tls12_webpki_roots() -> ClientConfig { use rustls::{OwnedTrustAnchor, RootCertStore}; let mut root_store = RootCertStore::empty(); root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { 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 } } trust-dns-proto-0.22.0/src/https/https_server.rs000064400000000000000000000106561046102023000200500ustar 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::borrow::Borrow; 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::https::HttpsError; /// 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: Arc, request: Request, ) -> Result where R: Stream> + 'static + Send + Debug + Unpin, { debug!("Received request: {:#?}", request); let this_server_name = this_server_name.borrow(); match crate::https::request::verify(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::https::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("ns.example.com", len).unwrap(); let request = request.map(|()| stream); let from_post = message_from(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); } } trust-dns-proto-0.22.0/src/https/mod.rs000064400000000000000000000014571046102023000160760ustar 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) const MIME_APPLICATION_DNS: &str = "application/dns-message"; const DNS_QUERY_PATH: &str = "/dns-query"; mod error; mod https_client_stream; pub mod https_server; pub mod request; pub mod response; pub use self::error::{Error as HttpsError, Result as HttpsResult}; pub use self::https_client_stream::{ HttpsClientConnect, HttpsClientResponse, HttpsClientStream, HttpsClientStreamBuilder, }; trust-dns-proto-0.22.0/src/https/request.rs000064400000000000000000000125251046102023000170050ustar 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, Version}; use tracing::debug; use crate::error::ProtoError; use crate::https::HttpsResult; /// Create a new Request for an http/2 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(name_server_name: &str, message_len: usize) -> HttpsResult> { // 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::https::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::HTTP_2) .header(CONTENT_TYPE, crate::https::MIME_APPLICATION_DNS) .header(ACCEPT, crate::https::MIME_APPLICATION_DNS) .header(CONTENT_LENGTH, message_len) .body(()) .map_err(|e| ProtoError::from(format!("h2 stream errored: {}", e)))?; Ok(request) } /// Verifies the request is something we know what to deal with pub fn verify(name_server: &str, request: &Request) -> HttpsResult<()> { // Verify all HTTP parameters let uri = request.uri(); // validate path if uri.path() != crate::https::DNS_QUERY_PATH { return Err(format!( "bad path: {}, expected: {}", uri.path(), crate::https::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(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::https::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::https::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::HTTP_2 { return Err("only HTTP/2 supported".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] fn test_new_verify() { let request = new("ns.example.com", 512).expect("error converting to http"); assert!(verify("ns.example.com", &request).is_ok()); } } trust-dns-proto-0.22.0/src/https/response.rs000064400000000000000000000042401046102023000171460ustar 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, Version}; use crate::error::ProtoError; use crate::https::HttpsResult; /// Create a new Response for an http/2 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(message_len: usize) -> HttpsResult> { Response::builder() .status(StatusCode::OK) .version(Version::HTTP_2) .header(CONTENT_TYPE, crate::https::MIME_APPLICATION_DNS) .header(CONTENT_LENGTH, message_len) .body(()) .map_err(|e| ProtoError::from(format!("invalid response: {}", e)).into()) } trust-dns-proto-0.22.0/src/lib.rs000064400000000000000000000203331046102023000147150ustar 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 )] #![recursion_limit = "2048"] #![cfg_attr(docsrs, feature(doc_cfg))] //! Trust-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 https; #[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")) } } trust-dns-proto-0.22.0/src/multicast/mdns_client_stream.rs000064400000000000000000000102611046102023000220250ustar 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) } } trust-dns-proto-0.22.0/src/multicast/mdns_stream.rs000064400000000000000000000715171046102023000205020ustar 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 lazy_static::lazy_static; 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; lazy_static! { /// mDNS ipv4 address https://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml pub static ref MDNS_IPV4: SocketAddr = 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 ref MDNS_IPV6: SocketAddr = 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 shouldnt' 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; lazy_static! { /// 250 appears to be unused/unregistered static ref TEST_MDNS_IPV4: IpAddr = Ipv4Addr::new(224,0,0,250).into(); /// FA appears to be unused/unregistered static ref TEST_MDNS_IPV6: IpAddr = 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."); } } trust-dns-proto-0.22.0/src/multicast/mod.rs000064400000000000000000000062671046102023000167450ustar 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 mod mdns_client_stream; mod mdns_stream; pub use self::mdns_client_stream::{MdnsClientConnect, MdnsClientStream}; 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, } } } trust-dns-proto-0.22.0/src/native_tls/mod.rs000064400000000000000000000011471046102023000171000ustar 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; trust-dns-proto-0.22.0/src/native_tls/tests.rs000064400000000000000000000215421046102023000174640ustar 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!("{}/tests/test-data/ca.der", server_path)); // Generate X509 certificate let dns_name = "ns.example.com"; let server_pkcs12_der = read_file(&format!("{}/tests/test-data/cert.p12", server_path)); // 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); // } trust-dns-proto-0.22.0/src/native_tls/tls_client_stream.rs000064400000000000000000000055321046102023000220360ustar 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, 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 /// /// # 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() } } trust-dns-proto-0.22.0/src/native_tls/tls_stream.rs000064400000000000000000000145021046102023000204750ustar 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::Connect; use crate::tcp::TcpStream; 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); } /// 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 stream = self.inner_build(name_server, dns_name, outbound_messages); (Box::pin(stream), message_sender) } async fn inner_build( self, name_server: SocketAddr, dns_name: String, outbound_messages: StreamReceiver, ) -> Result, io::Error> { use crate::native_tls::tls_stream; let ca_chain = self.ca_chain.clone(); let identity = self.identity; let tcp_stream = S::connect_with_bind(name_server, self.bind_addr).await; // 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, )) } } trust-dns-proto-0.22.0/src/op/edns.rs000064400000000000000000000201331046102023000155140ustar 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 * * http://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. */ //! Extended DNS options use std::fmt; use crate::error::*; use crate::rr::rdata::opt::{self, EdnsCode, EdnsOption}; use crate::rr::rdata::OPT; use crate::rr::{DNSClass, Name, RData, Record, RecordType}; use crate::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.rr_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::()?; opt::emit(encoder, &self.options)?; 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()); } trust-dns-proto-0.22.0/src/op/header.rs000064400000000000000000000566551046102023000160350ustar 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); } trust-dns-proto-0.22.0/src/op/message.rs000064400000000000000000001171671046102023000162250ustar 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. //! 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 datastructure, 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.rr_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.rr_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.rr_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.rr_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 trust_dns_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 require futher 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(()) } } #[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] #[rustfmt::skip] fn test_legit_message() { 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 namesservers, 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()); } trust-dns-proto-0.22.0/src/op/mod.rs000064400000000000000000000022531046102023000153450ustar 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 * * http://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; pub mod message; pub mod op_code; pub mod query; pub mod response_code; 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; trust-dns-proto-0.22.0/src/op/op_code.rs000064400000000000000000000060521046102023000161770ustar 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 std::convert::From; /// use trust_dns_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 std::convert::From; /// use trust_dns_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()), } } } trust-dns-proto-0.22.0/src/op/query.rs000064400000000000000000000247501046102023000157410ustar 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 * * http://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_u16(query_class_value & !MDNS_UNICAST_RESPONSE)? } else { DNSClass::from_u16(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()); } trust-dns-proto-0.22.0/src/op/response_code.rs000064400000000000000000000337101046102023000174200ustar 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 * * http://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 2845](https://tools.ietf.org/html/rfc2845) 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 2845](https://tools.ietf.org/html/rfc2845) BADSIG, /// Key not recognized [RFC 2845](https://tools.ietf.org/html/rfc2845) BADKEY, /// Signature out of time window [RFC 2845](https://tools.ietf.org/html/rfc2845) 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 unregisterd 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 std::convert::From; /// use trust_dns_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 std::convert::From; /// use trust_dns_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), } } } trust-dns-proto-0.22.0/src/openssl/mod.rs000064400000000000000000000011771046102023000164160ustar 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}; trust-dns-proto-0.22.0/src/openssl/tls_client_stream.rs000064400000000000000000000065031046102023000213500ustar 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, 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 /// /// # 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) } } impl Default for TlsClientStreamBuilder { fn default() -> Self { Self::new() } } trust-dns-proto-0.22.0/src/openssl/tls_server.rs000064400000000000000000000115011046102023000200170ustar 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::{ParsedPkcs12, 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<((X509, Option>), PKey)> { 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.parse(password.unwrap_or("")).map_err(|e| { ProtoError::from(format!( "failed to open pkcs12 from: {}: {}", path.display(), e )) })?; Ok(((parsed.cert, parsed.chain), 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()) } trust-dns-proto-0.22.0/src/openssl/tls_stream.rs000064400000000000000000000221021046102023000200030ustar 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; use openssl::pkey::{PKeyRef, Private}; use openssl::ssl::{ConnectConfiguration, SslConnector, SslContextBuilder, SslMethod, SslOptions}; use openssl::stack::Stack; use openssl::x509::store::X509StoreBuilder; use openssl::x509::{X509Ref, X509}; use tokio_openssl::{self, SslStream as TokioTlsStream}; use crate::iocompat::{AsyncIoStdAsTokio, AsyncIoTokioAsStd}; use crate::tcp::Connect; use crate::tcp::TcpStream; use crate::xfer::BufDnsStreamHandle; pub(crate) trait TlsIdentityExt { fn identity(&mut self, pkcs12: &ParsedPkcs12) -> io::Result<()> { self.identity_parts(&pkcs12.cert, &pkcs12.pkey, pkcs12.chain.as_ref()) } fn identity_parts( &mut self, cert: &X509Ref, pkey: &PKeyRef, chain: Option<&Stack>, ) -> io::Result<()>; } impl TlsIdentityExt for SslContextBuilder { fn identity_parts( &mut self, cert: &X509Ref, pkey: &PKeyRef, chain: Option<&Stack>, ) -> io::Result<()> { self.set_certificate(cert)?; 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( tls_config: ConnectConfiguration, dns_name: String, name_server: SocketAddr, bind_addr: Option, ) -> Result>, io::Error> { let tcp = S::connect_with_bind(name_server, bind_addr) .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); } /// 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 (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(tls_config, dns_name, name_server, self.bind_addr).map_ok(move |s| { TcpStream::from_stream_with_receiver( AsyncIoTokioAsStd(s), name_server, outbound_messages, ) }), ); (stream, message_sender) } } trust-dns-proto-0.22.0/src/quic/mod.rs000064400000000000000000000014261046102023000156710ustar 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; mod quic_stream; pub use self::quic_client_stream::{ client_config_tls13_webpki_roots, QuicClientConnect, QuicClientResponse, QuicClientStream, QuicClientStreamBuilder, }; pub use self::quic_server::{QuicServer, QuicStreams}; pub use self::quic_stream::{DoqErrorCode, QuicStream}; #[cfg(test)] mod tests; trust-dns-proto-0.22.0/src/quic/quic_client_stream.rs000064400000000000000000000267741046102023000210010ustar 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, NewConnection, OpenBi, TransportConfig, VarInt}; use rustls::{version::TLS13, ClientConfig as TlsClientConfig}; use crate::{ error::ProtoError, 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(stream: OpenBi, message: DnsRequest) -> Result { let (send_stream, recv_stream) = stream.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?; let response = stream.receive().await?; Ok(response.into()) } } 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") } let connection = self.quic_connection.open_bi(); Box::pin(Self::inner_send(connection, 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: TlsClientConfig, 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 = 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 _) } 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 (mut endpoint, _incoming) = Endpoint::new(endpoint_config, None, socket)?; // ensure the ALPN protocol is set correctly let mut crypto_config = self.crypto_config; 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 = self.transport_config; 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 connection = if early_data_enabled { match connecting.into_0rtt() { Ok((new_connection, _)) => new_connection, Err(connecting) => connecting.await?, } } else { connecting.await? }; let NewConnection { connection: quic_connection, .. } = connection; 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_webpki_roots() -> TlsClientConfig { use rustls::{OwnedTrustAnchor, RootCertStore}; let mut root_store = RootCertStore::empty(); root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { OwnedTrustAnchor::from_subject_spki_name_constraints( ta.subject, ta.spki, ta.name_constraints, ) })); 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)); let client_config = client_config_tls13_webpki_roots(); Self { crypto_config: client_config, 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) } } trust-dns-proto-0.22.0/src/quic/quic_config.rs000064400000000000000000000024371046102023000174030ustar 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(0x45acu16 as u64) .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 } trust-dns-proto-0.22.0/src/quic/quic_server.rs000064400000000000000000000073461046102023000174500ustar 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 futures_util::StreamExt; use quinn::{Endpoint, Incoming, IncomingBiStreams, 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, incoming: Incoming, } 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, incoming) = Endpoint::new(endpoint_config, Some(server_config), socket)?; Ok(Self { endpoint, incoming }) } /// 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.incoming.next().await { Some(conn) => conn, None => return Ok(None), }; let remote_addr = connecting.remote_address(); let conn = connecting.await?; let streams = QuicStreams { incoming_bi_streams: conn.bi_streams, }; Ok(Some((streams, 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 { incoming_bi_streams: IncomingBiStreams, } impl QuicStreams { /// Get the next bi directional stream from the client pub async fn next(&mut self) -> Option> { match self.incoming_bi_streams.next().await? { Ok((send_stream, receive_stream)) => { Some(Ok(QuicStream::new(send_stream, receive_stream))) } Err(e) => Some(Err(e.into())), } } } trust-dns-proto-0.22.0/src/quic/quic_stream.rs000064400000000000000000000214071046102023000174270ustar 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::convert::{TryFrom, TryInto}; use bytes::{Bytes, BytesMut}; use quinn::{RecvStream, SendStream, VarInt}; use tracing::debug; use crate::{ error::{ProtoError, ProtoErrorKind}, op::Message, }; /// ```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(message) } // 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)) } } trust-dns-proto-0.22.0/src/quic/tests.rs000064400000000000000000000076201046102023000162560ustar 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::{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) .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!( "{}/tests/test-data/ca.pem", server_path ))) .map_err(|e| format!("error reading cert: {}", e)) .unwrap(); let cert = tls_server::read_cert(Path::new(&format!( "{}/tests/test-data/cert.pem", server_path ))) .map_err(|e| format!("error reading cert: {}", e)) .unwrap(); let key = tls_server::read_key_from_pem(Path::new(&format!( "{}/tests/test-data/cert-key.pem", server_path ))) .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(); } trust-dns-proto-0.22.0/src/rr/dns_class.rs000064400000000000000000000117421046102023000165470ustar 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::convert::From; use std::fmt; use std::fmt::{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), } impl FromStr for DNSClass { type Err = ProtoError; /// Convert from `&str` to `DNSClass` /// /// ``` /// use std::str::FromStr; /// use trust_dns_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_uppercase(&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 trust_dns_proto::rr::dns_class::DNSClass; /// /// let var = DNSClass::from_u16(1).unwrap(); /// assert_eq!(DNSClass::IN, var); /// ``` 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), _ => Err(ProtoErrorKind::UnknownDnsClassValue(value).into()), } } /// 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 { Self::from_u16( decoder.read_u16()?.unverified(/*DNSClass is verified as safe in processing this*/), ) } } // TODO make these a macro or annotation /// Convert from `DNSClass` to `&str` /// /// ``` /// use trust_dns_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", } } } /// Convert from `DNSClass` to `u16` /// /// ``` /// use trust_dns_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), } } } 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)) } } #[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); } trust-dns-proto-0.22.0/src/rr/dnssec/algorithm.rs000064400000000000000000000254141046102023000200440ustar 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](http://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 { // http://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); } } trust-dns-proto-0.22.0/src/rr/dnssec/digest_type.rs000064400000000000000000000135201046102023000203710ustar 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, } } } trust-dns-proto-0.22.0/src/rr/dnssec/ec_public_key.rs000064400000000000000000000033011046102023000206420ustar 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] } } trust-dns-proto-0.22.0/src/rr/dnssec/mod.rs000064400000000000000000000045131046102023000166320ustar 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 * * http://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 nsec3; pub mod public_key; pub mod rdata; #[cfg(any(feature = "openssl", feature = "ring"))] mod rsa_public_key; mod supported_algorithm; pub mod tbs; mod trust_anchor; 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; #[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![] } } trust-dns-proto-0.22.0/src/rr/dnssec/nsec3.rs000064400000000000000000000241061046102023000170660ustar 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 * * http://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" (http://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]" /// (http://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()) } trust-dns-proto-0.22.0/src/rr/dnssec/public_key.rs000064400000000000000000000531421046102023000202030ustar 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]); } } trust-dns-proto-0.22.0/src/rr/dnssec/rdata/dnskey.rs000064400000000000000000000467231046102023000204540ustar 00000000000000/* * Copyright (C) 2016 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 * * http://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. */ //! public key record data for signing zone records use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::error::*; use crate::rr::dnssec::{Algorithm, Digest, DigestType}; use crate::rr::record_data::RData; use crate::rr::Name; use crate::serialize::binary::{ BinDecodable, BinDecoder, BinEncodable, BinEncoder, Restrict, RestrictedMath, }; /// [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(|_| emit(&mut encoder, self)) { 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, self)?; } 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 /// trust-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)) } } /// Read the RData from the given Decoder pub fn read(decoder: &mut BinDecoder<'_>, rdata_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 = rdata_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(DNSKEY::new( zone_key, secure_entry_point, revoke, algorithm, public_key, )) } /// Write the RData from the given Decoder pub fn emit(encoder: &mut BinEncoder<'_>, rdata: &DNSKEY) -> ProtoResult<()> { encoder.emit_u16(rdata.flags())?; encoder.emit(3)?; // always 3 for now rdata.algorithm().emit(encoder)?; encoder.emit_vec(rdata.public_key())?; Ok(()) } /// [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!(emit(&mut encoder, &rdata).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {:?}", bytes); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let read_rdata = read(&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); } } } trust-dns-proto-0.22.0/src/rr/dnssec/rdata/ds.rs000064400000000000000000000302551046102023000175560ustar 00000000000000/* * Copyright (C) 2016 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 * * http://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. */ //! 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::*; use crate::rr::dnssec::{Algorithm, DigestType}; use crate::serialize::binary::*; use crate::rr::dnssec::rdata::DNSKEY; use crate::rr::Name; /// [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()) } } /// Read the RData from the given Decoder pub fn read(decoder: &mut BinDecoder<'_>, rdata_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 = rdata_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(DS::new(key_tag, algorithm, digest_type, digest)) } /// Write the RData from the given Decoder pub fn emit(encoder: &mut BinEncoder<'_>, rdata: &DS) -> ProtoResult<()> { encoder.emit_u16(rdata.key_tag())?; rdata.algorithm().emit(encoder)?; // always 3 for now encoder.emit(rdata.digest_type().into())?; encoder.emit_vec(rdata.digest())?; Ok(()) } /// [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!(emit(&mut encoder, &rdata).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 = read(&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()); } } trust-dns-proto-0.22.0/src/rr/dnssec/rdata/key.rs000064400000000000000000001070111046102023000177330ustar 00000000000000/* * Copyright (C) 2016 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 * * http://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. */ //! 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::*; use crate::rr::dnssec::Algorithm; use crate::rr::record_data::RData; use crate::serialize::binary::*; /// [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 /// authenticatably 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 (Trust-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)) } } /// Read the RData from the given Decoder pub fn read(decoder: &mut BinDecoder<'_>, rdata_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 = rdata_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(KEY::new( key_trust, key_usage, signatory, protocol, algorithm, public_key, )) } /// Write the RData from the given Decoder pub fn emit(encoder: &mut BinEncoder<'_>, rdata: &KEY) -> ProtoResult<()> { encoder.emit_u16(rdata.flags())?; encoder.emit(u8::from(rdata.protocol))?; rdata.algorithm().emit(encoder)?; encoder.emit_vec(rdata.public_key())?; Ok(()) } /// 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 verticle 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 symbolicly. 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!(emit(&mut encoder, &rdata).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 = read(&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()); } } trust-dns-proto-0.22.0/src/rr/dnssec/rdata/mod.rs000064400000000000000000000743461046102023000177400ustar 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 * * http://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. */ //! 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 dnskey; pub mod ds; #[allow(deprecated)] pub mod key; pub mod nsec; pub mod nsec3; pub mod nsec3param; pub mod sig; pub mod tsig; use enum_as_inner::EnumAsInner; use tracing::trace; use crate::error::*; use crate::rr::rdata::null; use crate::rr::rdata::NULL; use crate::rr::{RData, RecordType}; use crate::serialize::binary::*; 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::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(DNSKEY), /// ```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(DS), /// ```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 / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// ``` 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"); dnskey::read(decoder, rdata_length).map(Self::CDNSKEY) } RecordType::CDS => { trace!("reading CDS"); ds::read(decoder, rdata_length).map(Self::CDS) } RecordType::DNSKEY => { trace!("reading DNSKEY"); dnskey::read(decoder, rdata_length).map(Self::DNSKEY) } RecordType::DS => { trace!("reading DS"); ds::read(decoder, rdata_length).map(Self::DS) } RecordType::KEY => { trace!("reading KEY"); key::read(decoder, rdata_length).map(Self::KEY) } RecordType::NSEC => { trace!("reading NSEC"); nsec::read(decoder, rdata_length).map(Self::NSEC) } RecordType::NSEC3 => { trace!("reading NSEC3"); nsec3::read(decoder, rdata_length).map(Self::NSEC3) } RecordType::NSEC3PARAM => { trace!("reading NSEC3PARAM"); nsec3param::read(decoder).map(Self::NSEC3PARAM) } RecordType::RRSIG => { trace!("reading RRSIG"); sig::read(decoder, rdata_length).map(Self::SIG) } RecordType::SIG => { trace!("reading SIG"); sig::read(decoder, rdata_length).map(Self::SIG) } RecordType::TSIG => { trace!("reading TSIG"); tsig::read(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| dnskey::emit(encoder, cdnskey)) } Self::CDS(ref cds) => encoder.with_canonical_names(|encoder| ds::emit(encoder, cds)), Self::DS(ref ds) => encoder.with_canonical_names(|encoder| ds::emit(encoder, ds)), Self::KEY(ref key) => encoder.with_canonical_names(|encoder| key::emit(encoder, key)), Self::DNSKEY(ref dnskey) => { encoder.with_canonical_names(|encoder| dnskey::emit(encoder, dnskey)) } Self::NSEC(ref nsec) => { encoder.with_canonical_names(|encoder| nsec::emit(encoder, nsec)) } Self::NSEC3(ref nsec3) => { encoder.with_canonical_names(|encoder| nsec3::emit(encoder, nsec3)) } Self::NSEC3PARAM(ref nsec3param) => { encoder.with_canonical_names(|encoder| nsec3param::emit(encoder, nsec3param)) } Self::SIG(ref sig) => encoder.with_canonical_names(|encoder| sig::emit(encoder, sig)), Self::TSIG(ref tsig) => tsig::emit(encoder, tsig), Self::Unknown { ref rdata, .. } => { encoder.with_canonical_names(|encoder| null::emit(encoder, rdata)) } } } 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::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<'_>, d: D) -> Result<(), fmt::Error> { write!(f, "{rdata}", rdata = d) } 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::TSIG(ref tsig) => w(f, tsig), Self::Unknown { rdata, .. } => w(f, rdata), } } } impl From for RData { fn from(rdata: DNSSECRData) -> Self { Self::DNSSEC(rdata) } } trust-dns-proto-0.22.0/src/rr/dnssec/rdata/nsec.rs000064400000000000000000000216731046102023000201040ustar 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 * * http://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. */ //! 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, RecordType}; use crate::serialize::binary::*; /// [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 } } /// Read the RData from the given Decoder pub fn read(decoder: &mut BinDecoder<'_>, rdata_length: Restrict) -> ProtoResult { let start_idx = decoder.index(); let next_domain_name = Name::read(decoder)?; let bit_map_len = rdata_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(NSEC::new(next_domain_name, record_types)) } /// [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. /// ``` pub fn emit(encoder: &mut BinEncoder<'_>, rdata: &NSEC) -> ProtoResult<()> { encoder.with_canonical_names(|encoder| { rdata.next_domain_name().emit(encoder)?; encode_type_bit_maps(encoder, rdata.type_bit_maps()) }) } /// [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!(emit(&mut encoder, &rdata).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 = read(&mut decoder, restrict).expect("Decoding error"); assert_eq!(rdata, read_rdata); } } trust-dns-proto-0.22.0/src/rr/dnssec/rdata/nsec3.rs000064400000000000000000000410121046102023000201540ustar 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 * * http://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. */ //! NSEC record types use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::error::*; use crate::rr::dnssec::Nsec3HashAlgorithm; use crate::rr::type_bit_map::*; use crate::rr::RecordType; use crate::serialize::binary::*; /// [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 } } /// Read the RData from the given Decoder pub fn read(decoder: &mut BinDecoder<'_>, rdata_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 = rdata_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 = rdata_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 = rdata_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(NSEC3::new( hash_algorithm, opt_out, iterations, salt, next_hashed_owner_name, record_types, )) } /// Write the RData from the given Decoder pub fn emit(encoder: &mut BinEncoder<'_>, rdata: &NSEC3) -> ProtoResult<()> { encoder.emit(rdata.hash_algorithm().into())?; encoder.emit(rdata.flags())?; encoder.emit_u16(rdata.iterations())?; encoder.emit(rdata.salt().len() as u8)?; encoder.emit_vec(rdata.salt())?; encoder.emit(rdata.next_hashed_owner_name().len() as u8)?; encoder.emit_vec(rdata.next_hashed_owner_name())?; encode_type_bit_maps(encoder, rdata.type_bit_maps())?; Ok(()) } /// [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!(emit(&mut encoder, &rdata).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 = read(&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!(emit(&mut encoder, &rdata_with_dups).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 = read(&mut decoder, restrict).expect("Decoding error"); assert_eq!(rdata_wo, read_rdata); } } trust-dns-proto-0.22.0/src/rr/dnssec/rdata/nsec3param.rs000064400000000000000000000217501046102023000212040ustar 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 * * http://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. */ //! parameters used for the nsec3 hash method use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::error::*; use crate::rr::dnssec::Nsec3HashAlgorithm; use crate::serialize::binary::*; /// [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 } } /// Read the RData from the given Decoder pub fn read(decoder: &mut BinDecoder<'_>) -> 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(NSEC3PARAM::new(hash_algorithm, opt_out, iterations, salt)) } /// Write the RData from the given Decoder pub fn emit(encoder: &mut BinEncoder<'_>, rdata: &NSEC3PARAM) -> ProtoResult<()> { encoder.emit(rdata.hash_algorithm().into())?; encoder.emit(rdata.flags())?; encoder.emit_u16(rdata.iterations())?; encoder.emit(rdata.salt().len() as u8)?; encoder.emit_vec(rdata.salt())?; Ok(()) } /// [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!(emit(&mut encoder, &rdata).is_ok()); let bytes = encoder.into_bytes(); println!("bytes: {:?}", bytes); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let read_rdata = read(&mut decoder).expect("Decoding error"); assert_eq!(rdata, read_rdata); } } trust-dns-proto-0.22.0/src/rr/dnssec/rdata/sig.rs000064400000000000000000000664441046102023000177430ustar 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 * * http://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. */ //! signature record for signing queries, updates, and responses use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::error::*; use crate::rr::dnssec::Algorithm; use crate::rr::{Name, RecordType}; use crate::serialize::binary::*; /// [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 } } /// Read the RData from the given Decoder pub fn read(decoder: &mut BinDecoder<'_>, rdata_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 = rdata_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(SIG::new( type_covered, algorithm, num_labels, original_ttl, sig_expiration, sig_inception, key_tag, signer_name, 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; /// ``` pub fn emit(encoder: &mut BinEncoder<'_>, sig: &SIG) -> ProtoResult<()> { let is_canonical_names = encoder.is_canonical_names(); sig.type_covered().emit(encoder)?; sig.algorithm().emit(encoder)?; encoder.emit(sig.num_labels())?; encoder.emit_u32(sig.original_ttl())?; encoder.emit_u32(sig.sig_expiration())?; encoder.emit_u32(sig.sig_inception())?; encoder.emit_u16(sig.key_tag())?; sig.signer_name() .emit_with_lowercase(encoder, is_canonical_names)?; encoder.emit_vec(sig.sig())?; Ok(()) } /// 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!(emit(&mut encoder, &rdata).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 = read(&mut decoder, restrict).expect("Decoding error"); assert_eq!(rdata, read_rdata); } } trust-dns-proto-0.22.0/src/rr/dnssec/rdata/tsig.rs000064400000000000000000001060141046102023000201130ustar 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. //! TSIG for secret key authentication of transaction #![allow(clippy::use_self)] use std::convert::TryInto; use std::fmt; #[cfg(feature = "serde-config")] use serde::{Deserialize, Serialize}; use crate::rr::rdata::sshfp; use crate::error::*; use crate::op::{Header, Message, Query}; use crate::rr::dns_class::DNSClass; use crate::rr::dnssec::rdata::DNSSECRData; use crate::rr::record_data::RData; use crate::rr::record_type::RecordType; use crate::rr::{Name, Record}; use crate::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 } } } /// 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 / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// ``` pub fn read(decoder: &mut BinDecoder<'_>, rdata_length: Restrict) -> ProtoResult { let end_idx = rdata_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(TSIG { algorithm, time, fudge, mac, oid, error, other, }) } /// 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 / /// / / /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// ``` pub fn emit(encoder: &mut BinEncoder<'_>, tsig: &TSIG) -> ProtoResult<()> { tsig.algorithm.emit(encoder)?; encoder.emit_u16( (tsig.time >> 32) .try_into() .map_err(|_| ProtoError::from("invalid time, overflow 48 bit counter in TSIG"))?, )?; encoder.emit_u32(tsig.time as u32)?; // this cast is supposed to truncate encoder.emit_u16(tsig.fudge)?; encoder.emit_u16( tsig.mac .len() .try_into() .map_err(|_| ProtoError::from("invalid mac, longer than 65535 B in TSIG"))?, )?; encoder.emit_vec(&tsig.mac)?; encoder.emit_u16(tsig.oid)?; encoder.emit_u16(tsig.error)?; encoder.emit_u16( tsig.other .len() .try_into() .map_err(|_| ProtoError::from("invalid other_buffer, longer than 65535 B in TSIG"))?, )?; encoder.emit_vec(&tsig.other)?; Ok(()) } // 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 trust-dns-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(ProtoError::from("unsupported mac algorithm")), }; 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(ProtoError::from("unsupported mac algorithm")), }; signer.update(message)?; signer.sign_to_vec().map_err(|e| e.into()) } // TODO: remove this once trust-dns-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(ProtoError::from("unsupported mac algorithm")), }; 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 trust-dns-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(ProtoError::from("unsupported mac algorithm")), }; 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(ProtoError::from("unsupported mac algorithm")), }; Ok(len) } /// Return true if cryptographic operations needed for using this algorithm are supported, /// false otherwise 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 callin 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.rr_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); emit(&mut encoder, &rdata).expect("failed to emit tsig"); let bytes = encoder.into_bytes(); println!("bytes: {:?}", bytes); let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); let read_rdata = read(&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("unkown_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") } } trust-dns-proto-0.22.0/src/rr/dnssec/roots/19036.rsa000064400000000000000000000004041046102023000200370ustar 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=trust-dns-proto-0.22.0/src/rr/dnssec/roots/20326.rsa000064400000000000000000000004041046102023000200310ustar 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{trust-dns-proto-0.22.0/src/rr/dnssec/roots/README.md000064400000000000000000000072331046102023000201340ustar 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 trust-dns-util v0.3.0-alpha.1 (file:///Users/benjaminfry/Development/rust/trust-dns/util) Finished dev [unoptimized + debuginfo] target(s) in 6.48s Running `/Users/benjaminfry/Development/rust/trust-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.trust-dns-proto-0.22.0/src/rr/dnssec/rsa_public_key.rs000064400000000000000000000023301046102023000210410ustar 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 } } trust-dns-proto-0.22.0/src/rr/dnssec/supported_algorithm.rs000064400000000000000000000207731046102023000221540ustar 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 * * http://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::convert::From; use std::fmt; use std::fmt::{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)); } trust-dns-proto-0.22.0/src/rr/dnssec/tbs.rs000064400000000000000000000241601046102023000166430ustar 00000000000000//! hash functions for DNSSec operations use super::rdata::{sig, DNSSECRData, SIG}; use crate::error::*; use crate::rr::dnssec::Algorithm; use crate::rr::{DNSClass, Name, RData, Record, RecordType}; use crate::serialize::binary::{BinEncodable, BinEncoder, EncodeMode}; /// 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 etity 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.rr_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(RData::DNSSEC(DNSSECRData::SIG(ref 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()) } trust-dns-proto-0.22.0/src/rr/dnssec/trust_anchor.rs000064400000000000000000000053661046102023000205750ustar 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 * * http://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)); } trust-dns-proto-0.22.0/src/rr/dnssec/verifier.rs000064400000000000000000000055301046102023000176660ustar 00000000000000//! Verifier is a structure for performing many of the signing processes of the DNSSec specification use crate::error::*; use crate::rr::dnssec::rdata::{DNSKEY, KEY, SIG}; use crate::rr::dnssec::Algorithm; use crate::rr::dnssec::{tbs, PublicKey, PublicKeyEnum}; use crate::rr::{DNSClass, Name, Record}; use crate::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: &SIG, 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()) } } trust-dns-proto-0.22.0/src/rr/domain/label.rs000064400000000000000000000356771046102023000171410ustar 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(format!("Label exceeds maximum length 63: {}", 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) .verify_dns_length(true) .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.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!("\\{:03o}", ch); 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