Skip to content

Commit 67637c4

Browse files
committed
Improve fmt::Display for common error variants
1 parent 2b1cf9b commit 67637c4

File tree

2 files changed

+174
-2
lines changed

2 files changed

+174
-2
lines changed

src/error.rs

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,75 @@ impl From<Error> for ControlFlow<Error, Error> {
316316

317317
impl fmt::Display for Error {
318318
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319-
write!(f, "{:?}", self)
319+
match self {
320+
Self::CertExpired { time, not_after } => write!(
321+
f,
322+
"certificate expired: current time is {}, \
323+
but certificate is not valid after {} \
324+
({} seconds ago)",
325+
time.as_secs(),
326+
not_after.as_secs(),
327+
time.as_secs().saturating_sub(not_after.as_secs())
328+
),
329+
330+
Self::CertNotValidYet { time, not_before } => write!(
331+
f,
332+
"certificate not valid yet: current time is {}, \
333+
but certificate is not valid before {} \
334+
({} seconds in future)",
335+
time.as_secs(),
336+
not_before.as_secs(),
337+
not_before.as_secs().saturating_sub(time.as_secs())
338+
),
339+
340+
Self::CrlExpired { time, next_update } => write!(
341+
f,
342+
"certificate revocation list expired: \
343+
current time is {}, \
344+
but CRL is not valid after {} \
345+
({} seconds ago)",
346+
time.as_secs(),
347+
next_update.as_secs(),
348+
time.as_secs().saturating_sub(next_update.as_secs())
349+
),
350+
351+
#[cfg(all(feature = "alloc", feature = "std"))]
352+
Self::CertNotValidForName(InvalidNameContext {
353+
expected,
354+
presented,
355+
}) => {
356+
write!(
357+
f,
358+
"certificate not valid for name: {:?} is required, but certificate ",
359+
expected.to_str()
360+
)?;
361+
362+
match presented.as_slice() {
363+
&[] => write!(
364+
f,
365+
"is not valid for any names (according to its subjectAltName extension)"
366+
),
367+
[one] => write!(f, "is only valid for {}", one),
368+
many => {
369+
write!(f, "is only valid for ")?;
370+
371+
let n = many.len();
372+
let all_but_last = &many[..n - 1];
373+
let last = &many[n - 1];
374+
375+
for (i, name) in all_but_last.iter().enumerate() {
376+
write!(f, "{}", name)?;
377+
if i < n - 2 {
378+
write!(f, ", ")?;
379+
}
380+
}
381+
write!(f, " or {}", last)
382+
}
383+
}
384+
}
385+
386+
other => write!(f, "{:?}", other),
387+
}
320388
}
321389
}
322390

@@ -371,3 +439,100 @@ pub enum DerTypeId {
371439
RevokedCertEntry,
372440
IssuingDistributionPoint,
373441
}
442+
443+
#[cfg(test)]
444+
mod tests {
445+
use super::*;
446+
use std::string::ToString;
447+
use std::time::Duration;
448+
449+
#[test]
450+
fn error_display() {
451+
assert_eq!(
452+
"certificate expired: current time is 320, \
453+
but certificate is not valid after 300 (20 seconds ago)",
454+
Error::CertExpired {
455+
time: UnixTime::since_unix_epoch(Duration::from_secs(320)),
456+
not_after: UnixTime::since_unix_epoch(Duration::from_secs(300)),
457+
}
458+
.to_string(),
459+
);
460+
461+
assert_eq!(
462+
"certificate not valid yet: current time is 300, \
463+
but certificate is not valid before 320 (20 seconds in future)",
464+
Error::CertNotValidYet {
465+
time: UnixTime::since_unix_epoch(Duration::from_secs(300)),
466+
not_before: UnixTime::since_unix_epoch(Duration::from_secs(320)),
467+
}
468+
.to_string(),
469+
);
470+
471+
assert_eq!(
472+
"certificate revocation list expired: current time \
473+
is 320, but CRL is not valid after 300 (20 seconds ago)",
474+
Error::CrlExpired {
475+
time: UnixTime::since_unix_epoch(Duration::from_secs(320)),
476+
next_update: UnixTime::since_unix_epoch(Duration::from_secs(300)),
477+
}
478+
.to_string(),
479+
);
480+
481+
// name errors
482+
#[cfg(all(feature = "alloc", feature = "std"))]
483+
{
484+
assert_eq!(
485+
"certificate not valid for name: \"example.com\" is required, \
486+
but certificate is not valid for any names (according to its \
487+
subjectAltName extension)",
488+
Error::CertNotValidForName(InvalidNameContext {
489+
expected: "example.com".try_into().unwrap(),
490+
presented: vec![],
491+
})
492+
.to_string(),
493+
);
494+
495+
assert_eq!(
496+
"certificate not valid for name: \"example.com\" is required, \
497+
but certificate is only valid for DnsName(\"foo.com\")",
498+
Error::CertNotValidForName(InvalidNameContext {
499+
expected: "example.com".try_into().unwrap(),
500+
presented: vec!["DnsName(\"foo.com\")".to_string(),],
501+
})
502+
.to_string(),
503+
);
504+
505+
assert_eq!(
506+
"certificate not valid for name: \"example.com\" is required, \
507+
but certificate is only valid for DnsName(\"foo.com\") \
508+
or DnsName(\"cowboy.org\")",
509+
Error::CertNotValidForName(InvalidNameContext {
510+
expected: "example.com".try_into().unwrap(),
511+
presented: vec![
512+
"DnsName(\"foo.com\")".to_string(),
513+
"DnsName(\"cowboy.org\")".to_string(),
514+
],
515+
})
516+
.to_string(),
517+
);
518+
519+
assert_eq!(
520+
"certificate not valid for name: \"example.com\" is required, \
521+
but certificate is only valid for DnsName(\"foo.com\"), \
522+
DnsName(\"cowboy.org\") or IpAddress(127.0.0.1)",
523+
Error::CertNotValidForName(InvalidNameContext {
524+
expected: "example.com".try_into().unwrap(),
525+
presented: vec![
526+
"DnsName(\"foo.com\")".to_string(),
527+
"DnsName(\"cowboy.org\")".to_string(),
528+
"IpAddress(127.0.0.1)".to_string()
529+
],
530+
})
531+
.to_string(),
532+
);
533+
}
534+
535+
// other errors (fall back to fmt::Debug)
536+
assert_eq!("BadDerTime", Error::BadDerTime.to_string());
537+
}
538+
}

tests/tls_server_certs.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ fn check_cert(
5050
for invalid in invalid_names {
5151
let name = ServerName::try_from(*invalid).unwrap();
5252
assert_eq!(
53-
cert.verify_is_valid_for_subject_name(&name),
53+
display_error(cert.verify_is_valid_for_subject_name(&name)),
5454
Err(webpki::Error::CertNotValidForName(InvalidNameContext {
5555
expected: name.to_owned(),
5656
presented: presented_names.iter().map(|n| n.to_string()).collect(),
@@ -61,6 +61,13 @@ fn check_cert(
6161
Ok(())
6262
}
6363

64+
fn display_error(r: Result<(), webpki::Error>) -> Result<(), webpki::Error> {
65+
if let Some(error) = r.as_ref().err() {
66+
println!("name error: {}", error);
67+
}
68+
r
69+
}
70+
6471
// DO NOT EDIT BELOW: generated by tests/generate.py
6572

6673
#[test]

0 commit comments

Comments
 (0)