1use anyhow::{bail, Context, Result};
16use std::fs::{metadata, set_permissions, OpenOptions};
17use std::io::{self, Read, Write};
18use std::os::unix::fs::PermissionsExt;
19use std::process::{Child, Command, Stdio};
20use std::thread::{self, JoinHandle};
21use tempfile::{self, TempDir};
22
23#[derive(Debug)]
24pub enum VerifyKeys {
25 Production,
27 #[cfg(test)]
29 InsecureTest,
30}
31
32#[derive(Debug)]
33enum VerifyReport {
34 Stderr,
36 StderrOnSuccess,
38 Ignore,
40}
41
42pub struct VerifyReader<R: Read> {
43 typ: VerifyType<R>,
44}
45
46enum VerifyType<R: Read> {
47 None(R),
48 Gpg(GpgReader<R>),
49}
50
51impl<R: Read> VerifyReader<R> {
52 pub fn new(source: R, gpg_signature: Option<&[u8]>, keys: VerifyKeys) -> Result<Self> {
53 let typ = if let Some(signature) = gpg_signature {
54 VerifyType::Gpg(GpgReader::new(source, signature, keys)?)
55 } else {
56 VerifyType::None(source)
57 };
58 Ok(VerifyReader { typ })
59 }
60
61 pub fn verify(&mut self) -> Result<()> {
64 match &mut self.typ {
65 VerifyType::None(_) => (),
66 VerifyType::Gpg(reader) => reader.finish(VerifyReport::Stderr)?,
67 }
68 Ok(())
69 }
70
71 pub fn verify_without_logging_failure(&mut self) -> Result<()> {
74 match &mut self.typ {
75 VerifyType::None(_) => (),
76 VerifyType::Gpg(reader) => reader.finish(VerifyReport::StderrOnSuccess)?,
77 }
78 Ok(())
79 }
80}
81
82impl<R: Read> Read for VerifyReader<R> {
83 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
84 match &mut self.typ {
85 VerifyType::None(reader) => reader.read(buf),
86 VerifyType::Gpg(reader) => reader.read(buf),
87 }
88 }
89}
90
91struct GpgReader<R: Read> {
92 _gpgdir: TempDir,
93 source: R,
94 child: Child,
95 stderr_thread: Option<JoinHandle<io::Result<Vec<u8>>>>,
96}
97
98impl<R: Read> GpgReader<R> {
99 fn new(source: R, signature: &[u8], keys: VerifyKeys) -> Result<Self> {
100 let gpgdir = tempfile::Builder::new()
102 .prefix("coreos-installer-")
103 .tempdir()
104 .context("creating temporary directory")?;
105 let meta = metadata(gpgdir.path()).context("getting metadata for temporary directory")?;
106 let mut permissions = meta.permissions();
107 permissions.set_mode(0o700);
108 set_permissions(gpgdir.path(), permissions)
109 .context("setting mode for temporary directory")?;
110
111 let keys = match keys {
113 VerifyKeys::Production => &include_bytes!("../signing-keys.asc")[..],
114 #[cfg(test)]
115 VerifyKeys::InsecureTest => {
116 &include_bytes!("../../fixtures/verify/test-key.pub.asc")[..]
117 }
118 };
119 let mut import = Command::new("gpg")
120 .arg("--homedir")
121 .arg(gpgdir.path())
122 .arg("--batch")
123 .arg("--quiet")
124 .arg("--import")
125 .stdin(Stdio::piped())
126 .spawn()
127 .context("running gpg --import")?;
128 import
129 .stdin
130 .as_mut()
131 .unwrap()
132 .write_all(keys)
133 .context("importing GPG keys")?;
134 if !import.wait().context("waiting for gpg --import")?.success() {
135 bail!("gpg --import failed");
136 }
137
138 let mut list = Command::new("gpg")
140 .arg("--homedir")
141 .arg(gpgdir.path())
142 .arg("--batch")
143 .arg("--list-keys")
144 .arg("--with-colons")
145 .stdout(Stdio::piped())
146 .spawn()
147 .context("running gpg --list-keys")?;
148 let mut list_output = String::new();
149 list.stdout
150 .as_mut()
151 .unwrap()
152 .read_to_string(&mut list_output)
153 .context("listing GPG keys")?;
154 if !list
155 .wait()
156 .context("waiting for gpg --list-keys")?
157 .success()
158 {
159 bail!("gpg --list-keys failed");
160 }
161
162 let mut trust: Vec<&str> = Vec::new();
164 for line in list_output.lines() {
165 let fields: Vec<&str> = line.split(':').collect();
166 if fields[0] != "pub" {
168 continue;
169 }
170 if fields.len() >= 5 {
172 trust.append(&mut vec!["--trusted-key", fields[4]]);
173 }
174 }
175
176 let trustdb = Command::new("gpg")
180 .arg("--homedir")
181 .arg(gpgdir.path())
182 .arg("--batch")
183 .arg("--check-trustdb")
184 .args(trust)
185 .output()
186 .context("running gpg --check-trustdb")?;
187 if !trustdb.status.success() {
188 eprint!("{}", String::from_utf8_lossy(&trustdb.stderr));
190 bail!("gpg --check-trustdb failed");
191 }
192
193 let mut signature_path = gpgdir.path().to_path_buf();
195 signature_path.push("signature");
196 let mut signature_file = OpenOptions::new()
197 .create_new(true)
198 .write(true)
199 .open(&signature_path)
200 .context("creating signature file")?;
201 signature_file
202 .write_all(signature)
203 .context("writing signature file")?;
204
205 let mut verify = Command::new("gpg")
207 .arg("--homedir")
208 .arg(gpgdir.path())
209 .arg("--batch")
210 .arg("--verify")
211 .arg(&signature_path)
212 .arg("-")
213 .stdin(Stdio::piped())
214 .stderr(Stdio::piped())
215 .spawn()
216 .context("running gpg --verify")?;
217
218 let mut stderr = verify.stderr.take().unwrap();
220 let stderr_thread = thread::Builder::new()
221 .name("gpg-stderr".into())
222 .spawn(move || -> io::Result<Vec<u8>> {
223 let mut buf = Vec::new();
224 stderr.read_to_end(&mut buf)?;
225 Ok(buf)
226 })
227 .context("spawning GPG stderr reader")?;
228
229 Ok(GpgReader {
230 _gpgdir: gpgdir,
231 source,
232 child: verify,
233 stderr_thread: Some(stderr_thread),
234 })
235 }
236
237 fn finish(&mut self, report: VerifyReport) -> io::Result<()> {
241 let wait_result = self.child.wait();
243 let join_result = self.stderr_thread.take().map(|t| t.join());
244
245 let success = wait_result?.success();
247 match join_result {
248 Some(Ok(Ok(stderr))) => match report {
250 VerifyReport::StderrOnSuccess if !success => (),
251 VerifyReport::Stderr | VerifyReport::StderrOnSuccess => {
254 eprint!("{}", String::from_utf8_lossy(&stderr))
255 }
256 VerifyReport::Ignore => (),
257 },
258 Some(Ok(Err(e))) => return Err(e),
260 Some(Err(e)) => std::panic::resume_unwind(e),
262 None => (),
264 }
265
266 if !success {
268 return Err(io::Error::new(
269 io::ErrorKind::InvalidData,
270 "GPG verification failure",
271 ));
272 }
273
274 Ok(())
275 }
276}
277
278impl<R: Read> Read for GpgReader<R> {
279 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
280 if buf.is_empty() {
281 return Ok(0);
282 }
283 let count = self.source.read(buf)?;
284 if count > 0 {
285 self.child
289 .stdin
290 .as_mut()
291 .unwrap()
292 .write_all(&buf[0..count])?;
293 }
294 Ok(count)
295 }
296}
297
298impl<R: Read> Drop for GpgReader<R> {
299 fn drop(&mut self) {
300 self.finish(VerifyReport::Ignore).ok();
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
312 fn test_good_signature() {
313 let data = include_bytes!("../../fixtures/verify/test-key.priv.asc");
314 let sig = include_bytes!("../../fixtures/verify/test-key.priv.asc.sig");
315
316 let mut reader =
317 VerifyReader::new(&data[..], Some(&sig[..]), VerifyKeys::InsecureTest).unwrap();
318 let mut buf = Vec::new();
319 reader.read_to_end(&mut buf).unwrap();
320 reader.verify().unwrap();
321 reader.verify().unwrap();
322 reader.verify_without_logging_failure().unwrap();
323 assert_eq!(&buf[..], &data[..]);
324 }
325
326 #[test]
328 fn test_bad_signature() {
329 let mut data = *include_bytes!("../../fixtures/verify/test-key.priv.asc");
330 let sig = include_bytes!("../../fixtures/verify/test-key.priv.asc.sig");
331 data[data.len() - 1] = b'!';
332
333 let mut reader =
334 VerifyReader::new(&data[..], Some(&sig[..]), VerifyKeys::InsecureTest).unwrap();
335 let mut buf = Vec::new();
336 reader.read_to_end(&mut buf).unwrap();
337 reader.verify().unwrap_err();
338 reader.verify().unwrap_err();
339 reader.verify_without_logging_failure().unwrap_err();
340 assert_eq!(&buf[..], &data[..]);
341 }
342
343 #[test]
345 fn test_truncated_data() {
346 let data = include_bytes!("../../fixtures/verify/test-key.priv.asc");
347 let sig = include_bytes!("../../fixtures/verify/test-key.priv.asc.sig");
348
349 let mut reader =
350 VerifyReader::new(&data[..1000], Some(&sig[..]), VerifyKeys::InsecureTest).unwrap();
351 let mut buf = Vec::new();
352 reader.read_to_end(&mut buf).unwrap();
353 reader.verify().unwrap_err();
354 reader.verify().unwrap_err();
355 reader.verify_without_logging_failure().unwrap_err();
356 assert_eq!(&buf[..], &data[..1000]);
357 }
358
359 #[test]
361 fn test_no_pubkey() {
362 let data = include_bytes!("../../fixtures/verify/test-key.priv.asc");
363 let sig = include_bytes!("../../fixtures/verify/test-key.priv.asc.random.sig");
364
365 let mut reader =
366 VerifyReader::new(&data[..], Some(&sig[..]), VerifyKeys::InsecureTest).unwrap();
367 let mut buf = Vec::new();
368 reader.read_to_end(&mut buf).unwrap();
369 reader.verify().unwrap_err();
370 reader.verify().unwrap_err();
371 reader.verify_without_logging_failure().unwrap_err();
372 assert_eq!(&buf[..], &data[..]);
373 }
374}