libcoreinst/io/
verify.rs

1// Copyright 2019 CoreOS, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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 keys
26    Production,
27    /// Snake oil key
28    #[cfg(test)]
29    InsecureTest,
30}
31
32#[derive(Debug)]
33enum VerifyReport {
34    /// Report verification result to stderr
35    Stderr,
36    /// Report verification result to stderr only if successful
37    StderrOnSuccess,
38    /// Verify silently
39    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    /// Return an error if signature verification fails, and report the
62    /// result to stderr
63    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    /// Return an error if signature verification fails.  Report the result
72    /// to stderr if verification is successful, but not if it fails.
73    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        // create GPG home directory with restrictive mode
101        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        // import public keys
112        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        // list the public keys we just imported
139        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        // accumulate key IDs into trust arguments
163        let mut trust: Vec<&str> = Vec::new();
164        for line in list_output.lines() {
165            let fields: Vec<&str> = line.split(':').collect();
166            // only look at public keys
167            if fields[0] != "pub" {
168                continue;
169            }
170            // extract key ID
171            if fields.len() >= 5 {
172                trust.append(&mut vec!["--trusted-key", fields[4]]);
173            }
174        }
175
176        // mark keys trusted in trustdb
177        // We do this as a separate pass to keep the resulting log lines
178        // out of the verify output.
179        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            // copy out its stderr
189            eprint!("{}", String::from_utf8_lossy(&trustdb.stderr));
190            bail!("gpg --check-trustdb failed");
191        }
192
193        // write signature to file
194        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        // start verification
206        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        // spawn stderr reader
219        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    /// Stop GPG, forward its stderr if requested, and check its exit status.
238    /// The exit status check happens on every call, but stderr forwarding
239    /// only happens on the first call.
240    fn finish(&mut self, report: VerifyReport) -> io::Result<()> {
241        // do cleanup first: wait for child process and join on thread
242        let wait_result = self.child.wait();
243        let join_result = self.stderr_thread.take().map(|t| t.join());
244
245        // possibly copy GPG's stderr to ours
246        let success = wait_result?.success();
247        match join_result {
248            // thread returned GPG's stderr
249            Some(Ok(Ok(stderr))) => match report {
250                VerifyReport::StderrOnSuccess if !success => (),
251                // use eprint rather than io::stderr() so the output is
252                // captured when running tests
253                VerifyReport::Stderr | VerifyReport::StderrOnSuccess => {
254                    eprint!("{}", String::from_utf8_lossy(&stderr))
255                }
256                VerifyReport::Ignore => (),
257            },
258            // thread returned error
259            Some(Ok(Err(e))) => return Err(e),
260            // thread panicked; propagate the panic
261            Some(Err(e)) => std::panic::resume_unwind(e),
262            // already joined the thread on a previous call
263            None => (),
264        }
265
266        // check GPG exit status
267        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            // On a partial write we return an error in violation of the
286            // API contract.  This should be okay, since it's a fatal error
287            // for us anyway.
288            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        // if we haven't already forwarded GPG's stderr, avoid doing it now,
301        // so we don't imply that we're checking the result
302        self.finish(VerifyReport::Ignore).ok();
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    /// Read data with valid signature
311    #[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    /// Read data with bad signature
327    #[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    /// Read truncated data with otherwise-valid signature
344    #[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    /// Read data with signing key not in keyring
360    #[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}