1use super::types::RndcKeyData;
7use anyhow::{Context, Result};
8use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
9use hickory_client::rr::rdata::tsig::TsigAlgorithm;
10use hickory_client::rr::Name;
11use hickory_proto::rr::dnssec::tsig::TSigner;
12use rand::Rng;
13use std::collections::BTreeMap;
14use std::str::FromStr;
15
16use crate::constants::TSIG_FUDGE_TIME_SECS;
17
18#[must_use]
22pub fn generate_rndc_key() -> RndcKeyData {
23 let mut rng = rand::thread_rng();
24 let mut key_bytes = [0u8; 32]; rng.fill(&mut key_bytes);
26
27 RndcKeyData {
28 name: String::new(), algorithm: crate::crd::RndcAlgorithm::HmacSha256,
30 secret: BASE64.encode(key_bytes),
31 }
32}
33
34#[must_use]
38pub fn create_rndc_secret_data(key_data: &RndcKeyData) -> BTreeMap<String, String> {
39 let mut data = BTreeMap::new();
40 data.insert("key-name".to_string(), key_data.name.clone());
41 data.insert(
42 "algorithm".to_string(),
43 key_data.algorithm.as_str().to_string(),
44 );
45 data.insert("secret".to_string(), key_data.secret.clone());
46
47 let rndc_key_content = format!(
49 "key \"{}\" {{\n algorithm {};\n secret \"{}\";\n}};\n",
50 key_data.name,
51 key_data.algorithm.as_str(),
52 key_data.secret
53 );
54 data.insert("rndc.key".to_string(), rndc_key_content);
55
56 data
57}
58
59pub fn parse_rndc_secret_data(data: &BTreeMap<String, Vec<u8>>) -> Result<RndcKeyData> {
72 if let (Some(name_bytes), Some(algo_bytes), Some(secret_bytes)) = (
74 data.get("key-name"),
75 data.get("algorithm"),
76 data.get("secret"),
77 ) {
78 let name = std::str::from_utf8(name_bytes)?.to_string();
79 let algorithm_str = std::str::from_utf8(algo_bytes)?;
80 let secret = std::str::from_utf8(secret_bytes)?.to_string();
81
82 let algorithm = match algorithm_str {
83 "hmac-md5" => crate::crd::RndcAlgorithm::HmacMd5,
84 "hmac-sha1" => crate::crd::RndcAlgorithm::HmacSha1,
85 "hmac-sha224" => crate::crd::RndcAlgorithm::HmacSha224,
86 "hmac-sha256" => crate::crd::RndcAlgorithm::HmacSha256,
87 "hmac-sha384" => crate::crd::RndcAlgorithm::HmacSha384,
88 "hmac-sha512" => crate::crd::RndcAlgorithm::HmacSha512,
89 _ => anyhow::bail!("Unsupported RNDC algorithm '{algorithm_str}'. Supported algorithms: hmac-md5, hmac-sha1, hmac-sha224, hmac-sha256, hmac-sha384, hmac-sha512"),
90 };
91
92 return Ok(RndcKeyData {
93 name,
94 algorithm,
95 secret,
96 });
97 }
98
99 if let Some(rndc_key_bytes) = data.get("rndc.key") {
101 let rndc_key_content = std::str::from_utf8(rndc_key_bytes)?;
102 return parse_rndc_key_file(rndc_key_content);
103 }
104
105 anyhow::bail!(
106 "Secret must contain either (key-name, algorithm, secret) or rndc.key field. \
107 For external secrets, provide only 'rndc.key' with the BIND9 key file content."
108 )
109}
110
111fn parse_rndc_key_file(content: &str) -> Result<RndcKeyData> {
125 let name = content
130 .lines()
131 .find(|line| line.contains("key"))
132 .and_then(|line| {
133 line.split('"').nth(1) })
135 .context("Failed to parse key name from rndc.key file")?
136 .to_string();
137
138 let algorithm_str = content
140 .lines()
141 .find(|line| line.contains("algorithm"))
142 .and_then(|line| {
143 line.split_whitespace()
144 .nth(1) .map(|s| s.trim_end_matches(';'))
146 })
147 .context("Failed to parse algorithm from rndc.key file")?;
148
149 let algorithm = match algorithm_str {
150 "hmac-md5" => crate::crd::RndcAlgorithm::HmacMd5,
151 "hmac-sha1" => crate::crd::RndcAlgorithm::HmacSha1,
152 "hmac-sha224" => crate::crd::RndcAlgorithm::HmacSha224,
153 "hmac-sha256" => crate::crd::RndcAlgorithm::HmacSha256,
154 "hmac-sha384" => crate::crd::RndcAlgorithm::HmacSha384,
155 "hmac-sha512" => crate::crd::RndcAlgorithm::HmacSha512,
156 _ => anyhow::bail!("Unsupported algorithm '{algorithm_str}' in rndc.key file"),
157 };
158
159 let secret = content
161 .lines()
162 .find(|line| line.contains("secret"))
163 .and_then(|line| {
164 line.split('"').nth(1) })
166 .context("Failed to parse secret from rndc.key file")?
167 .to_string();
168
169 Ok(RndcKeyData {
170 name,
171 algorithm,
172 secret,
173 })
174}
175
176pub fn create_tsig_signer(key_data: &RndcKeyData) -> Result<TSigner> {
182 let algorithm = match key_data.algorithm {
184 crate::crd::RndcAlgorithm::HmacMd5 => TsigAlgorithm::HmacMd5,
185 crate::crd::RndcAlgorithm::HmacSha1 => TsigAlgorithm::HmacSha1,
186 crate::crd::RndcAlgorithm::HmacSha224 => TsigAlgorithm::HmacSha224,
187 crate::crd::RndcAlgorithm::HmacSha256 => TsigAlgorithm::HmacSha256,
188 crate::crd::RndcAlgorithm::HmacSha384 => TsigAlgorithm::HmacSha384,
189 crate::crd::RndcAlgorithm::HmacSha512 => TsigAlgorithm::HmacSha512,
190 };
191
192 let key_bytes = BASE64
194 .decode(&key_data.secret)
195 .context("Failed to decode TSIG key")?;
196
197 let signer = TSigner::new(
199 key_bytes,
200 algorithm,
201 Name::from_str(&key_data.name).context("Invalid TSIG key name")?,
202 u16::try_from(TSIG_FUDGE_TIME_SECS).unwrap_or(300),
203 )
204 .context("Failed to create TSIG signer")?;
205
206 Ok(signer)
207}
208
209#[cfg(test)]
210#[path = "rndc_tests.rs"]
211mod rndc_tests;