1pub const REMOTE_TLS_CA_CERT_SETTING: &str = "HEDDLE_REMOTE_TLS_CA_CERT";
6
7pub fn is_tls_trust_failure(message: &str) -> bool {
9 let normalized = message.to_ascii_lowercase();
10 normalized.contains("invalid peer certificate")
11 || normalized.contains("unknownissuer")
12 || normalized.contains("unknown issuer")
13 || normalized.contains("certificateunknown")
14}
15
16pub fn annotate_tls_trust_failure(error: impl std::fmt::Display) -> String {
18 let message = error.to_string();
19 if is_tls_trust_failure(&message) && !message.contains(REMOTE_TLS_CA_CERT_SETTING) {
20 format!(
21 "{message}; trust this server's CA with {REMOTE_TLS_CA_CERT_SETTING}=/path/to/ca.pem"
22 )
23 } else {
24 message
25 }
26}
27
28pub fn annotate_error_chain_tls_trust_failure(error: &(dyn std::error::Error + 'static)) -> String {
30 let mut message = error.to_string();
31 let mut current = error.source();
32 let mut trust_failure = is_tls_trust_failure(&message);
33 while let Some(err) = current {
34 let next = err.to_string();
35 trust_failure |= is_tls_trust_failure(&next);
36 if !message.contains(&next) {
37 message.push_str(": ");
38 message.push_str(&next);
39 }
40 current = err.source();
41 }
42 if trust_failure {
43 annotate_tls_trust_failure(message)
44 } else {
45 message
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::{REMOTE_TLS_CA_CERT_SETTING, annotate_tls_trust_failure, is_tls_trust_failure};
52
53 #[test]
54 fn unknown_issuer_is_a_tls_trust_failure() {
55 assert!(is_tls_trust_failure(
56 "invalid peer certificate: UnknownIssuer"
57 ));
58 assert!(!is_tls_trust_failure("connection refused"));
59 }
60
61 #[test]
62 fn annotation_names_the_ca_setting_once() {
63 let annotated = annotate_tls_trust_failure("invalid peer certificate: UnknownIssuer");
64 assert!(annotated.contains(REMOTE_TLS_CA_CERT_SETTING));
65 assert_eq!(
66 annotate_tls_trust_failure(annotated.as_str()),
67 annotated,
68 "already-annotated messages must stay stable"
69 );
70 }
71
72 #[test]
73 fn annotation_walks_a_source_chain() {
74 #[derive(Debug)]
75 struct Root(&'static str);
76 impl std::fmt::Display for Root {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 f.write_str(self.0)
79 }
80 }
81 impl std::error::Error for Root {}
82
83 #[derive(Debug)]
84 struct Wrapper {
85 source: Root,
86 }
87 impl std::fmt::Display for Wrapper {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.write_str("error sending request")
90 }
91 }
92 impl std::error::Error for Wrapper {
93 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
94 Some(&self.source)
95 }
96 }
97
98 let error = Wrapper {
99 source: Root("invalid peer certificate: UnknownIssuer"),
100 };
101 let annotated = super::annotate_error_chain_tls_trust_failure(&error);
102 assert!(annotated.contains("error sending request"));
103 assert!(annotated.contains("UnknownIssuer"));
104 assert!(annotated.contains(REMOTE_TLS_CA_CERT_SETTING));
105 }
106}