1use log::debug;
4use once_cell::sync::Lazy;
5use sha2::{Digest, Sha256};
6use std::{
7 io::{self, Write},
8 str,
9 sync::Mutex,
10};
11use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor};
12use x509_parser::parse_x509_certificate;
13use yubikey::{certificate::Certificate, piv::*, YubiKey};
14
15#[macro_export]
17macro_rules! status_ok {
18 ($status:expr, $msg:expr) => {
19 $crate::terminal::Status::new()
20 .justified()
21 .bold()
22 .color(termcolor::Color::Green)
23 .status($status)
24 .print_stdout($msg);
25 };
26 ($status:expr, $fmt:expr, $($arg:tt)+) => {
27 $crate::status_ok!($status, format!($fmt, $($arg)+));
28 };
29}
30
31#[macro_export]
33macro_rules! status_warn {
34 ($msg:expr) => {
35 $crate::terminal::Status::new()
36 .bold()
37 .color(termcolor::Color::Yellow)
38 .status("warning:")
39 .print_stdout($msg);
40 };
41 ($fmt:expr, $($arg:tt)+) => {
42 $crate::status_warn!(format!($fmt, $($arg)+));
43 };
44}
45
46#[macro_export]
48macro_rules! status_err {
49 ($msg:expr) => {
50 $crate::terminal::Status::new()
51 .bold()
52 .color(termcolor::Color::Red)
53 .status("error:")
54 .print_stderr($msg);
55 };
56 ($fmt:expr, $($arg:tt)+) => {
57 $crate::status_err!(format!($fmt, $($arg)+));
58 };
59}
60
61static COLOR_CHOICE: Lazy<Mutex<Option<ColorChoice>>> = Lazy::new(|| Mutex::new(None));
63
64pub static STDOUT: Lazy<StandardStream> = Lazy::new(|| StandardStream::stdout(get_color_choice()));
66
67pub static STDERR: Lazy<StandardStream> = Lazy::new(|| StandardStream::stderr(get_color_choice()));
69
70fn get_color_choice() -> ColorChoice {
74 let choice = COLOR_CHOICE.lock().unwrap();
75 *choice
76 .as_ref()
77 .expect("terminal stream accessed before initialized!")
78}
79
80pub(super) fn set_color_choice(color_choice: ColorChoice) {
84 let mut choice = COLOR_CHOICE.lock().unwrap();
85 assert!(choice.is_none(), "terminal colors already configured!");
86 *choice = Some(color_choice);
87}
88
89#[derive(Clone, Debug, Default)]
91pub struct Status {
92 justified: bool,
94
95 bold: bool,
97
98 color: Option<Color>,
100
101 status: Option<String>,
103}
104
105impl Status {
106 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn justified(mut self) -> Self {
113 self.justified = true;
114 self
115 }
116
117 pub fn bold(mut self) -> Self {
119 self.bold = true;
120 self
121 }
122
123 pub fn color(mut self, c: Color) -> Self {
125 self.color = Some(c);
126 self
127 }
128
129 pub fn status<S>(mut self, msg: S) -> Self
131 where
132 S: ToString,
133 {
134 self.status = Some(msg.to_string());
135 self
136 }
137
138 pub fn print_stdout(self, msg: impl AsRef<str>) {
140 self.print(&STDOUT, msg).expect("error printing to stdout!")
141 }
142
143 pub fn print_stderr(self, msg: impl AsRef<str>) {
145 self.print(&STDERR, msg).expect("error printing to stderr!")
146 }
147
148 fn print(self, stream: &StandardStream, msg: impl AsRef<str>) -> io::Result<()> {
150 let mut s = stream.lock();
151 s.reset()?;
152 s.set_color(ColorSpec::new().set_fg(self.color).set_bold(self.bold))?;
153
154 if let Some(status) = self.status {
155 if self.justified {
156 write!(s, "{:>12}", status)?;
157 } else {
158 write!(s, "{}", status)?;
159 }
160 }
161
162 s.reset()?;
163 writeln!(s, " {}", msg.as_ref())?;
164 s.flush()?;
165
166 Ok(())
167 }
168}
169
170pub fn print_cert_info(
172 yubikey: &mut YubiKey,
173 slot: SlotId,
174 stream: &mut StandardStreamLock<'_>,
175) -> io::Result<()> {
176 let cert = match Certificate::read(yubikey, slot) {
177 Ok(c) => c,
178 Err(e) => {
179 debug!("error reading certificate in slot {:?}: {}", slot, e);
180 return Ok(());
181 }
182 };
183 let buf = cert.into_buffer();
184
185 if !buf.is_empty() {
186 let fingerprint = Sha256::digest(&buf);
187 let slot_id: u8 = slot.into();
188 print_cert_attr(stream, "Slot", format!("{:x}", slot_id))?;
189 match parse_x509_certificate(&buf) {
190 Ok((_rem, cert)) => {
191 print_cert_attr(
192 stream,
193 "Algorithm",
194 cert.tbs_certificate.subject_pki.algorithm.algorithm,
195 )?;
196
197 print_cert_attr(stream, "Subject", cert.tbs_certificate.subject)?;
198 print_cert_attr(stream, "Issuer", cert.tbs_certificate.issuer)?;
199 print_cert_attr(
200 stream,
201 "Fingerprint",
202 &hex::upper::encode_string(&fingerprint),
203 )?;
204 print_cert_attr(
205 stream,
206 "Not Before",
207 cert.tbs_certificate
208 .validity
209 .not_before
210 .to_rfc2822()
211 .unwrap(),
212 )?;
213 print_cert_attr(
214 stream,
215 "Not After",
216 cert.tbs_certificate
217 .validity
218 .not_after
219 .to_rfc2822()
220 .unwrap(),
221 )?;
222 }
223 _ => {
224 println!("Failed to parse certificate");
225 return Ok(());
226 }
227 };
228 }
229
230 Ok(())
231}
232
233fn print_cert_attr(
235 stream: &mut StandardStreamLock<'_>,
236 name: &str,
237 value: impl ToString,
238) -> io::Result<()> {
239 stream.set_color(ColorSpec::new().set_bold(true))?;
240 write!(stream, "{:>12}:", name)?;
241 stream.reset()?;
242 writeln!(stream, " {}", value.to_string())?;
243 stream.flush()?;
244 Ok(())
245}