1 //! General utility modules for use all over the code base
2 use std::{cmp::Ordering, fmt, net::IpAddr, str::FromStr};
3
4 use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
5
6 use bytes::Bytes;
7 use rpki::{
8 repository::crypto::DigestAlgorithm,
9 uri::{Https, Rsync},
10 };
11
12 use crate::constants::KRILL_VERSION;
13
14 #[cfg(feature = "hsm")]
15 pub mod dummysigner;
16 pub mod ext_serde;
17 pub mod file;
18 pub mod httpclient;
19 pub mod softsigner;
20 pub mod xml;
21
22 //------------ KrillVersion --------------------------------------------------
23
24 /// Defines a Krill version. Will
25
26 #[derive(Clone, Debug, Eq, PartialEq)]
27 pub struct KrillVersion {
28 major: u64,
29 minor: u64,
30 patch: u64,
31 release_type: KrillVersionReleaseType,
32 }
33
34 impl KrillVersion {
current() -> Self35 pub fn current() -> Self {
36 // Note: we have a unit test to ensure that the KRILL_VERSION constant
37 // which is derived from the Cargo.toml version can be parsed.
38 Self::from_str(KRILL_VERSION).unwrap()
39 }
40
v0_5_0_or_before() -> Self41 pub fn v0_5_0_or_before() -> Self {
42 Self::dev(0, 5, 0, "or-before".to_string())
43 }
44
release(major: u64, minor: u64, patch: u64) -> Self45 pub fn release(major: u64, minor: u64, patch: u64) -> Self {
46 KrillVersion {
47 major,
48 minor,
49 patch,
50 release_type: KrillVersionReleaseType::Release,
51 }
52 }
53
candidate(major: u64, minor: u64, patch: u64, number: u64) -> Self54 pub fn candidate(major: u64, minor: u64, patch: u64, number: u64) -> Self {
55 KrillVersion {
56 major,
57 minor,
58 patch,
59 release_type: KrillVersionReleaseType::Candidate(number),
60 }
61 }
62
dev(major: u64, minor: u64, patch: u64, addition: String) -> Self63 fn dev(major: u64, minor: u64, patch: u64, addition: String) -> Self {
64 KrillVersion {
65 major,
66 minor,
67 patch,
68 release_type: KrillVersionReleaseType::Dev(addition),
69 }
70 }
71 }
72
73 impl FromStr for KrillVersion {
74 type Err = KrillVersionParseError;
75
from_str(s: &str) -> Result<Self, Self::Err>76 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 // x.y.z => major.minor.patch release
78 // x.y.z-rc# => major.minor.patch release candidate #
79 // x.y.z-<str> => major.minor.patch dev 'str'
80 // other => cannot parse
81 //
82 // Support legacy enum based version notation as well:
83 // V0_6 => 0.6.0
84
85 let parts: Vec<&str> = s.split('.').collect();
86 if parts.len() == 3 {
87 let major = u64::from_str(parts[0]).map_err(|_| KrillVersionParseError::for_str(s))?;
88
89 let minor = u64::from_str(parts[1]).map_err(|_| KrillVersionParseError::for_str(s))?;
90
91 let mut patch_parts = parts[2].split('-');
92
93 let patch = u64::from_str(patch_parts.next().unwrap()).map_err(|_| KrillVersionParseError::for_str(s))?;
94
95 match patch_parts.next() {
96 None => Ok(KrillVersion::release(major, minor, patch)),
97 Some(addition) => {
98 if addition.len() > 2 && addition.starts_with("rc") {
99 let number = u64::from_str(&addition[2..]).map_err(|_| KrillVersionParseError::for_str(s))?;
100 Ok(KrillVersion::candidate(major, minor, patch, number))
101 } else {
102 Ok(KrillVersion::dev(major, minor, patch, addition.to_string()))
103 }
104 }
105 }
106 } else {
107 match s {
108 // Enums present in versions before 0.9.1
109 "V0_6" => Ok(KrillVersion::release(0, 6, 0)),
110 "V0_7" => Ok(KrillVersion::release(0, 7, 0)),
111 "V0_8_0_RC1" => Ok(KrillVersion::candidate(0, 8, 0, 1)),
112 "V0_8" => Ok(KrillVersion::release(0, 8, 0)),
113 "V0_8_1_RC1" => Ok(KrillVersion::candidate(0, 8, 1, 1)),
114 "V0_8_1" => Ok(KrillVersion::release(0, 8, 1)),
115 "V0_8_2" => Ok(KrillVersion::release(0, 8, 2)),
116 "V0_9_0_RC1" => Ok(KrillVersion::candidate(0, 9, 0, 1)),
117 "V0_9_0" => Ok(KrillVersion::release(0, 9, 0)),
118 _ => Err(KrillVersionParseError::for_str(s)),
119 }
120 }
121 }
122 }
123
124 impl fmt::Display for KrillVersion {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result125 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126 write!(f, "{}.{}.{}{}", self.major, self.minor, self.patch, self.release_type)
127 }
128 }
129
130 #[derive(Clone, Debug)]
131 pub struct KrillVersionParseError(String);
132
133 impl KrillVersionParseError {
for_str(s: &str) -> Self134 fn for_str(s: &str) -> Self {
135 KrillVersionParseError(s.to_string())
136 }
137 }
138
139 impl fmt::Display for KrillVersionParseError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 write!(f, "Could not parse Krill version from string: {}", self.0)
142 }
143 }
144
145 impl Ord for KrillVersion {
cmp(&self, other: &Self) -> Ordering146 fn cmp(&self, other: &Self) -> Ordering {
147 let mut res = self.major.cmp(&other.major);
148
149 // use res.is_eq() when the minimum rust requirement will be 1.53 or higher
150 if res == Ordering::Equal {
151 res = self.minor.cmp(&other.minor);
152 }
153
154 if res == Ordering::Equal {
155 res = self.patch.cmp(&other.patch);
156 }
157
158 if res == Ordering::Equal {
159 res = self.release_type.cmp(&other.release_type);
160 }
161
162 res
163 }
164 }
165
166 impl PartialOrd for KrillVersion {
partial_cmp(&self, other: &Self) -> Option<Ordering>167 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
168 Some(self.cmp(other))
169 }
170 }
171
172 impl Serialize for KrillVersion {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,173 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
174 where
175 S: Serializer,
176 {
177 self.to_string().serialize(serializer)
178 }
179 }
180
181 impl<'de> Deserialize<'de> for KrillVersion {
deserialize<D>(deserializer: D) -> std::result::Result<KrillVersion, D::Error> where D: Deserializer<'de>,182 fn deserialize<D>(deserializer: D) -> std::result::Result<KrillVersion, D::Error>
183 where
184 D: Deserializer<'de>,
185 {
186 let string = String::deserialize(deserializer)?;
187 KrillVersion::from_str(string.as_str()).map_err(de::Error::custom)
188 }
189 }
190
191 #[derive(Clone, Debug, Eq, PartialEq)]
192 enum KrillVersionReleaseType {
193 Release,
194 Candidate(u64),
195 Dev(String),
196 }
197
198 impl Ord for KrillVersionReleaseType {
cmp(&self, other: &Self) -> Ordering199 fn cmp(&self, other: &Self) -> Ordering {
200 match &self {
201 KrillVersionReleaseType::Release => match other {
202 KrillVersionReleaseType::Release => Ordering::Equal,
203 _ => Ordering::Greater,
204 },
205 KrillVersionReleaseType::Candidate(nr) => match other {
206 KrillVersionReleaseType::Release => Ordering::Less,
207 KrillVersionReleaseType::Candidate(nr_other) => nr.cmp(nr_other),
208 &KrillVersionReleaseType::Dev(_) => Ordering::Greater,
209 },
210 KrillVersionReleaseType::Dev(_) => match other {
211 KrillVersionReleaseType::Dev(_) => Ordering::Equal,
212 _ => Ordering::Less,
213 },
214 }
215 }
216 }
217
218 impl PartialOrd for KrillVersionReleaseType {
partial_cmp(&self, other: &Self) -> Option<Ordering>219 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
220 Some(self.cmp(other))
221 }
222 }
223
224 impl fmt::Display for KrillVersionReleaseType {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result225 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
226 match self {
227 KrillVersionReleaseType::Release => write!(f, ""),
228 KrillVersionReleaseType::Candidate(nr) => write!(f, "-rc{}", nr),
229 KrillVersionReleaseType::Dev(text) => write!(f, "-{}", text),
230 }
231 }
232 }
233
234 /// Returns the SHA256 hash for the given octets.
sha256(object: &[u8]) -> Bytes235 pub fn sha256(object: &[u8]) -> Bytes {
236 let digest = DigestAlgorithm::default().digest(object);
237 Bytes::copy_from_slice(digest.as_ref())
238 }
239
240 // TODO: check that an IP address is_global() when that stabilizes: https://github.com/rust-lang/rust/issues/27709
241 /// Assumes that non-ip hostnames are global (they may of course resolve to something that isn't but hey we tried to help)
seems_global_uri(auth: &str) -> bool242 fn seems_global_uri(auth: &str) -> bool {
243 if auth.to_lowercase() == "localhost" || auth.starts_with('[') || IpAddr::from_str(auth).is_ok() {
244 false
245 } else if let Some(i) = auth.rfind(':') {
246 let auth = &auth[0..i];
247 IpAddr::from_str(auth).is_err()
248 } else {
249 // appears to be a non-ip hostname, assume it's global
250 true
251 }
252 }
253
254 pub trait AllowedUri {
authority(&self) -> &str255 fn authority(&self) -> &str;
256
seems_global_uri(&self) -> bool257 fn seems_global_uri(&self) -> bool {
258 seems_global_uri(self.authority())
259 }
260 }
261
262 impl AllowedUri for Rsync {
authority(&self) -> &str263 fn authority(&self) -> &str {
264 self.authority()
265 }
266 }
267
268 impl AllowedUri for Https {
authority(&self) -> &str269 fn authority(&self) -> &str {
270 self.authority()
271 }
272 }
273
274 //------------ Tests ---------------------------------------------------------
275
276 #[cfg(test)]
277 mod tests {
278
279 use crate::commons::util::seems_global_uri;
280
281 use super::*;
282
283 #[test]
check_uri_seems_global()284 fn check_uri_seems_global() {
285 // Does not seem global
286 assert!(!seems_global_uri("localhost"));
287 assert!(!seems_global_uri("0.0.0.0"));
288 assert!(!seems_global_uri("127.0.0.1"));
289 assert!(!seems_global_uri("127.0.0.1:873"));
290 assert!(!seems_global_uri("1.2.3.4"));
291 assert!(!seems_global_uri("::"));
292 assert!(!seems_global_uri("::1"));
293 assert!(!seems_global_uri("[::1]:873"));
294 assert!(!seems_global_uri("2001:0db8:85a3:0000:0000:8a2e:0370:7334"));
295
296 // Looks ok
297 assert!(seems_global_uri("localghost"));
298 assert!(seems_global_uri("rpki.bla"));
299 }
300
301 #[test]
krill_version_from_current_cargo_version()302 fn krill_version_from_current_cargo_version() {
303 KrillVersion::current();
304 }
305
306 #[test]
krill_version_pre_0_9_1_enums()307 fn krill_version_pre_0_9_1_enums() {
308 KrillVersion::from_str("V0_6").unwrap();
309 KrillVersion::from_str("V0_7").unwrap();
310 KrillVersion::from_str("V0_8_0_RC1").unwrap();
311 KrillVersion::from_str("V0_8").unwrap();
312 KrillVersion::from_str("V0_8_1_RC1").unwrap();
313 KrillVersion::from_str("V0_8_1").unwrap();
314 KrillVersion::from_str("V0_8_2").unwrap();
315 KrillVersion::from_str("V0_9_0_RC1").unwrap();
316 KrillVersion::from_str("V0_9_0").unwrap();
317 }
318
319 #[test]
krill_version_from_str()320 fn krill_version_from_str() {
321 KrillVersion::from_str("0.9.1").unwrap();
322 KrillVersion::from_str("0.9.1-rc1").unwrap();
323 KrillVersion::from_str("0.9.1-bis").unwrap();
324
325 // We do not support short, or random notations including but not limited to:
326 assert!(KrillVersion::from_str("v0.9.1").is_err());
327 assert!(KrillVersion::from_str("0.9-bis").is_err());
328 assert!(KrillVersion::from_str("some garbage").is_err());
329 }
330
331 #[test]
krill_version_ordering()332 fn krill_version_ordering() {
333 let v0_9_1 = KrillVersion::from_str("0.9.1").unwrap();
334 let v0_9_1_rc1 = KrillVersion::from_str("0.9.1-rc1").unwrap();
335 let v0_9_1_rc2 = KrillVersion::from_str("0.9.1-rc2").unwrap();
336 let v0_9_1_dev = KrillVersion::from_str("0.9.1-dev").unwrap();
337
338 assert!(v0_9_1 > v0_9_1_rc1);
339 assert!(v0_9_1_rc2 > v0_9_1_rc1);
340 assert!(v0_9_1_rc1 > v0_9_1_dev);
341
342 let v0_9_0 = KrillVersion::from_str("0.9.0").unwrap();
343 assert!(v0_9_1 > v0_9_0);
344 assert!(v0_9_1_rc2 > v0_9_0);
345 assert!(v0_9_1_dev > v0_9_0);
346 }
347 }
348