sequoia_openpgp/parse/
hashed_reader.rs

1use std::io;
2use std::cmp;
3use std::mem;
4use std::fmt;
5
6use buffered_reader::BufferedReader;
7use buffered_reader::buffered_reader_generic_read_impl;
8
9use crate::fmt::hex;
10use crate::packet::Signature;
11use crate::parse::{Cookie, HashesFor, Hashing};
12use crate::Result;
13use crate::types::HashAlgorithm;
14use crate::types::SignatureType;
15
16const TRACE : bool = false;
17
18/// Controls line-ending normalization during hashing.
19///
20/// OpenPGP normalizes line endings when signing or verifying text
21/// signatures.
22#[derive(Clone, Eq)]
23pub(crate) enum HashingMode<T> {
24    /// Hash for a binary signature.
25    ///
26    /// The data is hashed as-is.
27    Binary(Vec<u8>, T),
28
29    /// Hash for a text signature.
30    ///
31    /// The data is hashed with line endings normalized to `\r\n`.
32    Text(Vec<u8>, T),
33
34    /// Like Text, but the last character that we hashed was a '\r'
35    /// that we converted to a '\r\n'.
36    TextLastWasCr(Vec<u8>, T),
37}
38
39impl<T: std::fmt::Debug> std::fmt::Debug for HashingMode<T> {
40    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
41        use self::HashingMode::*;
42        match self {
43            Binary(salt, t) if salt.is_empty() =>
44                write!(f, "Binary({:?})", t),
45            Binary(salt, t) =>
46                write!(f, "Binary({}, {:?})", hex::encode(salt), t),
47            Text(salt, t) if salt.is_empty() =>
48                write!(f, "Text({:?})", t),
49            Text(salt, t) =>
50                write!(f, "Text({}, {:?})", hex::encode(salt), t),
51            TextLastWasCr(salt, t) if salt.is_empty() =>
52                write!(f, "Text(last was CR, {:?})", t),
53            TextLastWasCr(salt, t) =>
54                write!(f, "Text(last was CR, {}, {:?})", hex::encode(salt), t),
55        }
56    }
57}
58
59impl<T: PartialEq> PartialEq for HashingMode<T> {
60    fn eq(&self, other: &Self) -> bool {
61        use self::HashingMode::*;
62        match (self, other) {
63            (Binary(salt_s, s), Binary(salt_o, o)) =>
64                salt_s == salt_o && s == o,
65
66            (Text(salt_s, s), Text(salt_o, o)) =>
67                salt_s == salt_o && s == o,
68            (TextLastWasCr(salt_s, s), Text(salt_o, o)) =>
69                salt_s == salt_o && s == o,
70            (Text(salt_s, s), TextLastWasCr(salt_o, o)) =>
71                salt_s == salt_o && s == o,
72            (TextLastWasCr(salt_s, s), TextLastWasCr(salt_o, o)) =>
73                salt_s == salt_o && s == o,
74
75            _ => false,
76        }
77    }
78}
79
80impl<T> HashingMode<T> {
81    pub(crate) fn map<U, F: Fn(&T) -> U>(&self, f: F) -> HashingMode<U> {
82        use self::HashingMode::*;
83        match self {
84            Binary(salt, t) => Binary(salt.clone(), f(t)),
85            Text(salt, t) => Text(salt.clone(), f(t)),
86            TextLastWasCr(salt, t) => TextLastWasCr(salt.clone(), f(t)),
87        }
88    }
89
90    pub(crate) fn mapf<U, F: Fn(T) -> Result<U>>(self, f: F)
91                                                 -> Result<HashingMode<U>> {
92        use self::HashingMode::*;
93        match self {
94            Binary(salt, t) => Ok(Binary(salt.clone(), f(t)?)),
95            Text(salt, t) => Ok(Text(salt.clone(), f(t)?)),
96            TextLastWasCr(salt, t) => Ok(TextLastWasCr(salt.clone(), f(t)?)),
97        }
98    }
99
100    pub(crate) fn salt(&self) -> &[u8] {
101        use self::HashingMode::*;
102        match self {
103            Binary(salt, _t) => salt,
104            Text(salt, _t) => salt,
105            TextLastWasCr(salt, _t) => salt,
106        }
107    }
108
109    pub(crate) fn as_ref(&self) -> &T {
110        use self::HashingMode::*;
111        match self {
112            Binary(_salt, t) => t,
113            Text(_salt, t) => t,
114            TextLastWasCr(_salt, t) => t,
115        }
116    }
117
118    pub(crate) fn as_mut(&mut self) -> &mut T {
119        use self::HashingMode::*;
120        match self {
121            Binary(_salt, t) => t,
122            Text(_salt, t) => t,
123            TextLastWasCr(_salt, t) => t,
124        }
125    }
126
127    pub(crate) fn for_signature(t: T, s: &Signature) -> Self {
128        match s {
129            Signature::V3(s) => Self::for_salt_and_type(t, &[], s.typ()),
130            Signature::V4(s) => Self::for_salt_and_type(t, &[], s.typ()),
131            Signature::V6(s) => Self::for_salt_and_type(t, s.salt(), s.typ()),
132        }
133    }
134    pub(crate) fn for_salt_and_type(t: T, salt: &[u8], typ: SignatureType)
135                                    -> Self
136    {
137        if typ == SignatureType::Text {
138            HashingMode::Text(salt.into(), t)
139        } else {
140            HashingMode::Binary(salt.into(), t)
141        }
142    }
143
144    pub(crate) fn into_inner(self) -> T {
145        use self::HashingMode::*;
146        match self {
147            Binary(_salt, t) => t,
148            Text(_salt, t) => t,
149            TextLastWasCr(_salt, t) => t,
150        }
151    }
152}
153
154impl HashingMode<crate::crypto::hash::Context>
155{
156    /// Updates the given hash context.  When in text mode, normalize
157    /// the line endings to "\r\n" on the fly.
158    pub(crate) fn update(&mut self, data: &[u8]) {
159        if data.is_empty() {
160            // This isn't just a short circuit.  It preserves
161            // `last_was_cr`, which running through the code would
162            // not.
163            return;
164        }
165
166        let (h, mut last_was_cr) = match self {
167            HashingMode::Text(_salt, h) => (h, false),
168            HashingMode::TextLastWasCr(_salt, h) => (h, true),
169            HashingMode::Binary(_salt, h) => return h.update(data),
170        };
171
172        let mut line = data;
173        let last_is_cr = line.last() == Some(&b'\r');
174        while ! line.is_empty() {
175            let mut next = 0;
176            for (i, c) in line.iter().cloned().enumerate() {
177                match c {
178                    b'\n' if last_was_cr => {
179                        // We already hash the \n.
180                        assert_eq!(i, 0);
181                        assert_eq!(next, 0);
182                        next = 1;
183                        break;
184                    },
185                    b'\r' | b'\n' => {
186                        h.update(&line[..i]);
187                        h.update(b"\r\n");
188                        next = i + 1;
189                        if c == b'\r' && line.get(next) == Some(&b'\n') {
190                            next += 1;
191                        }
192                        break;
193                    },
194                    _ => (),
195                }
196                last_was_cr = false;
197            }
198
199            if next > 0 {
200                line = &line[next..];
201            } else {
202                h.update(line);
203                break;
204            }
205        }
206
207        match (&mut *self, last_is_cr) {
208            (&mut HashingMode::Text(_, _), false) => {
209                // This is the common case.  Getting a crlf that is
210                // split across two chunks is extremely rare.  Hence,
211                // the clones used to change the variant are rarely
212                // needed.
213            },
214            (&mut HashingMode::Text(ref mut salt, ref mut h), true) => {
215                *self =
216                    HashingMode::TextLastWasCr(std::mem::take(salt), h.clone());
217            }
218            (&mut HashingMode::TextLastWasCr(ref mut salt, ref mut h), false) =>
219            {
220                *self = HashingMode::Text(std::mem::take(salt), h.clone());
221            },
222            (&mut HashingMode::TextLastWasCr(_, _), true) => (),
223
224            _ => unreachable!("handled above"),
225        }
226    }
227}
228
229pub(crate) struct HashedReader<R: BufferedReader<Cookie>> {
230    reader: R,
231    cookie: Cookie,
232}
233
234impl<R: BufferedReader<Cookie>> fmt::Display for HashedReader<R> {
235    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
236        write!(f, "HashedReader")
237    }
238}
239
240impl<R: BufferedReader<Cookie>> fmt::Debug for HashedReader<R> {
241    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
242        f.debug_struct("HashedReader")
243            .field("cookie", &self.cookie)
244            .field("reader", &self.reader)
245            .finish()
246    }
247}
248
249impl<R: BufferedReader<Cookie>> HashedReader<R> {
250    /// Instantiates a new hashed reader.  `hashes_for` is the hash's
251    /// purpose.  `algos` is a list of algorithms for which we should
252    /// compute the hash.
253    pub fn new(reader: R, hashes_for: HashesFor,
254               algos: Vec<HashingMode<HashAlgorithm>>)
255            -> Result<Self> {
256        let mut cookie = Cookie::default();
257
258        for mode in algos {
259            let salt = mode.salt().to_vec();
260            let mode = mode.mapf(|algo| {
261                let mut ctx = algo.context()?
262                // XXX: This is not quite correct, but since this is
263                // only important for hashing keys, which we don't do
264                // in streaming operation, we can get away with it.
265                    .for_digest();
266                ctx.update(&salt);
267                Ok(ctx)
268            })?;
269
270            cookie.sig_group_mut().hashes.push(mode);
271        }
272
273        cookie.hashes_for = hashes_for;
274
275        Ok(HashedReader {
276            reader,
277            cookie,
278        })
279    }
280}
281
282impl Cookie {
283    fn hash_update(&mut self, data: &[u8]) {
284        let level = self.level.unwrap_or(0);
285        let hashes_for = self.hashes_for;
286        let ngroups = self.sig_groups.len();
287
288        tracer!(TRACE, "Cookie::hash_update", level);
289        t!("({} bytes, {} hashes, enabled: {:?})",
290           data.len(), self.sig_group().hashes.len(), self.hashing);
291
292        if self.hashes_for == HashesFor::CleartextSignature {
293            return self.hash_update_csf(data);
294        }
295
296        // Hash stashed data first.
297        if let Some(stashed_data) = self.hash_stash.take() {
298            // The stashed data was supposed to be hashed into the
299            // then-topmost signature-group's hash, but wasn't,
300            // because framing isn't hashed into the topmost signature
301            // group.  By the time the parser encountered a new
302            // signature group, the data has already been consumed.
303            // We fix that here by hashing the stashed data into the
304            // former topmost signature-group's hash.
305            assert!(ngroups > 1);
306            for h in self.sig_groups[ngroups-2].hashes.iter_mut()
307            {
308                t!("({:?}): group {} {:?} hashing {} stashed bytes.",
309                   hashes_for, ngroups-2,
310                   h.map(|ctx| ctx.algo()),
311                   data.len());
312
313                h.update(&stashed_data);
314            }
315        }
316
317        if data.is_empty() {
318            return;
319        }
320
321        if self.hashing == Hashing::Disabled {
322            t!("    hash_update: NOT hashing {} bytes: {}.",
323               data.len(), crate::fmt::to_hex(data, true));
324            return;
325        }
326
327        let topmost_group = |i| i == ngroups - 1;
328        for (i, sig_group) in self.sig_groups.iter_mut().enumerate() {
329            if topmost_group(i) && self.hashing != Hashing::Enabled {
330                t!("topmost group {} NOT hashing {} bytes: {}.",
331                   i, data.len(), crate::fmt::to_hex(data, true));
332
333                return;
334            }
335
336            for h in sig_group.hashes.iter_mut() {
337                t!("{:?}: group {} {:?} hashing {} bytes.",
338                   hashes_for, i, h.map(|ctx| ctx.algo()), data.len());
339                h.update(data);
340            }
341        }
342    }
343
344    fn hash_update_csf(&mut self, data: &[u8]) {
345        let level = self.level.unwrap_or(0);
346        let hashes_for = self.hashes_for;
347        let ngroups = self.sig_groups.len();
348
349        assert_eq!(self.hashes_for, HashesFor::CleartextSignature);
350        // There is exactly one group.  However, this can momentarily
351        // be violated if there are One-Pass-Signature packets in the
352        // signature block.  This doesn't last long though: the
353        // message parser will reject the message because it doesn't
354        // adhere to the grammar.
355        assert!(ngroups == 1 || ngroups == /* momentarily */ 2);
356
357        tracer!(TRACE, "Cookie::hash_update_csf", level);
358        t!("Cleartext Signature Framework message");
359
360        if data.is_empty() {
361            return;
362        }
363
364        if self.hashing == Hashing::Disabled {
365            t!("    hash_update: NOT hashing {} bytes: {}.",
366               data.len(), crate::fmt::to_hex(data, true));
367            return;
368        }
369
370        // Hash the data.
371        for h in self.sig_groups[0].hashes.iter_mut() {
372            t!("{:?}: {:?} hashing {} bytes.",
373               hashes_for, h.map(|ctx| ctx.algo()), data.len());
374            h.update(data);
375        }
376    }
377}
378
379impl<T: BufferedReader<Cookie>> io::Read for HashedReader<T> {
380    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
381        buffered_reader_generic_read_impl(self, buf)
382    }
383}
384
385// Wrap a BufferedReader so that any data that is consumed is added to
386// the hash.
387impl<R: BufferedReader<Cookie>>
388        BufferedReader<Cookie> for HashedReader<R> {
389    fn buffer(&self) -> &[u8] {
390        self.reader.buffer()
391    }
392
393    fn data(&mut self, amount: usize) -> io::Result<&[u8]> {
394        self.reader.data(amount)
395    }
396
397    fn data_hard(&mut self, amount: usize) -> io::Result<&[u8]> {
398        self.reader.data_hard(amount)
399    }
400
401    fn consume(&mut self, amount: usize) -> &[u8] {
402        // We need to take the state rather than get a mutable
403        // reference to it, because self.reader.buffer() requires a
404        // reference as well.
405        let mut state = self.cookie_set(Cookie::default());
406
407        {
408            // The inner buffered reader must return at least `amount`
409            // bytes, because the caller can't `consume(amount)` if
410            // the internal buffer doesn't have at least that many
411            // bytes.
412            let data = self.reader.buffer();
413            assert!(data.len() >= amount);
414            state.hash_update(&data[..amount]);
415        }
416
417        self.cookie_set(state);
418
419        self.reader.consume(amount)
420    }
421
422    fn data_consume(&mut self, amount: usize) -> io::Result<&[u8]> {
423        // See consume() for an explanation of the following
424        // acrobatics.
425
426        let mut state = self.cookie_set(Cookie::default());
427
428        let got = {
429            let data = self.reader.data(amount)?;
430            let data = &data[..cmp::min(data.len(), amount)];
431            state.hash_update(data);
432            data.len()
433        };
434
435        self.cookie_set(state);
436
437        if let Ok(data) = self.reader.data_consume(amount) {
438            assert!(data.len() >= got);
439            Ok(data)
440        } else {
441            panic!("reader.data_consume() returned less than reader.data()!");
442        }
443    }
444
445    fn data_consume_hard(&mut self, amount: usize) -> io::Result<&[u8]> {
446        // See consume() for an explanation of the following
447        // acrobatics.
448
449        let mut state = self.cookie_set(Cookie::default());
450
451        {
452            let data = self.reader.data_hard(amount)?;
453            assert!(data.len() >= amount);
454            state.hash_update(&data[..amount]);
455        }
456
457        self.cookie_set(state);
458
459        let result = self.reader.data_consume(amount);
460        assert!(result.is_ok());
461        result
462    }
463
464    fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<Cookie>> {
465        Some(&mut self.reader)
466    }
467
468    fn get_ref(&self) -> Option<&dyn BufferedReader<Cookie>> {
469        Some(&self.reader)
470    }
471
472    fn into_inner<'b>(self: Box<Self>)
473            -> Option<Box<dyn BufferedReader<Cookie> + 'b>>
474            where Self: 'b {
475        Some(self.reader.into_boxed())
476    }
477
478    fn cookie_set(&mut self, cookie: Cookie) -> Cookie {
479        mem::replace(&mut self.cookie, cookie)
480    }
481
482    fn cookie_ref(&self) -> &Cookie {
483        &self.cookie
484    }
485
486    fn cookie_mut(&mut self) -> &mut Cookie {
487        &mut self.cookie
488    }
489}
490
491/// Hashes the given buffered reader.
492///
493/// This can be used to verify detached signatures.  For a more
494/// convenient method, see [`DetachedVerifier`].
495///
496///  [`DetachedVerifier`]: crate::parse::stream::DetachedVerifier
497pub(crate) fn hash_buffered_reader<R>(reader: R,
498                                      algos: &[HashingMode<HashAlgorithm>])
499    -> Result<Vec<HashingMode<crate::crypto::hash::Context>>>
500    where R: BufferedReader<crate::parse::Cookie>,
501{
502    let mut reader
503        = HashedReader::new(reader, HashesFor::Signature, algos.to_vec())?;
504
505    // Hash all the data.
506    reader.drop_eof()?;
507
508    let hashes =
509        mem::take(&mut reader.cookie_mut().sig_group_mut().hashes);
510    Ok(hashes)
511}
512
513#[cfg(test)]
514mod test {
515    use super::*;
516
517    use buffered_reader::BufferedReader;
518
519    #[test]
520    fn hash_test_1() {
521        use std::collections::HashMap;
522        struct Test<'a> {
523            data: &'a [u8],
524            expected: HashMap<HashAlgorithm, &'a str>,
525        }
526
527        let tests = [
528            Test {
529                data: &b"foobar\n"[..],
530                expected: [
531                    (HashAlgorithm::SHA1,
532                     "988881adc9fc3655077dc2d4d757d480b5ea0e11"),
533                ].iter().cloned().collect(),
534            },
535            Test {
536                data: &b"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\n"[..],
537                expected: [
538                    (HashAlgorithm::SHA1,
539                     "1d12c55b3a85daab4776a1df41a8f30ada099e11"),
540                    (HashAlgorithm::SHA224,
541                     "a4c1bde77c682a0e9e30c6afdd1ece2397ffeec61dde2a0eaa23191e"),
542                    (HashAlgorithm::SHA256,
543                    "151a1d51a1870dc244f07f4844f46ee65fae19a8efeb60b203a074aff899e27d"),
544                    (HashAlgorithm::SHA384,
545                    "5bea68c8c696bbed95e152d61c446ad0e05bf68f7df39cbfeae568bee6f6691c840fb1d5dd2599737b08dbb33eed344b"),
546                    (HashAlgorithm::SHA512,
547                     "5fa032487774082af5cc833c2db5f943e31cc75cd2bfaa7d9bbd0ccabf5403b6dbcb484254727a524588f20e9ef336d8ce8533332c5ac1b9d50af3003a0da8d8"),
548                ].iter().filter(|(hash, _)| hash.is_supported()).cloned().collect(),
549            },
550        ];
551
552        for test in tests.iter() {
553            let reader
554                = buffered_reader::Generic::with_cookie(
555                    test.data, None, Default::default());
556            let mut reader
557                = HashedReader::new(reader, HashesFor::MDC,
558                                    test.expected.keys().cloned()
559                                    .map(|v| HashingMode::Binary(vec![], v))
560                                    .collect()).unwrap();
561
562            assert_eq!(reader.steal_eof().unwrap(), test.data);
563
564            let cookie = reader.cookie_mut();
565
566            let mut hashes = std::mem::take(&mut cookie.sig_group_mut().hashes);
567            for mode in hashes.iter_mut() {
568                let hash = mode.as_mut();
569                let algo = hash.algo();
570                let mut digest = vec![0u8; hash.digest_size()];
571                let _ = hash.digest(&mut digest);
572
573                assert_eq!(digest,
574                           &crate::fmt::from_hex(test.expected.get(&algo)
575                                                    .unwrap(), true)
576                           .unwrap()[..],
577                           "Algo: {:?}", algo);
578            }
579        }
580    }
581
582    #[test]
583    fn hash_update_text() -> crate::Result<()> {
584        for text in &[
585            "one\r\ntwo\r\nthree",
586            "one\ntwo\nthree",
587            "one\rtwo\rthree",
588            "one\ntwo\r\nthree",
589        ] {
590            for chunk_size in &[ text.len(), 1 ] {
591                let mut ctx
592                    = HashingMode::Text(vec![],
593                                        HashAlgorithm::SHA256.context()?
594                                        .for_digest());
595                for chunk in text.as_bytes().chunks(*chunk_size) {
596                    ctx.update(chunk);
597                }
598                let mut ctx = ctx.into_inner();
599                let mut digest = vec![0; ctx.digest_size()];
600                let _ = ctx.digest(&mut digest);
601                assert_eq!(
602                    &crate::fmt::hex::encode(&digest),
603                    "5536758151607BB81CE8D6F49189B2E84763DA9EA84965AB7327E704DAE415EB",
604                    "{:?}, chunk size: {}", text, chunk_size);
605            }
606        }
607        Ok(())
608    }
609
610    #[test]
611    fn hash_reader_test() {
612        use std::collections::HashMap;
613
614        let expected: HashMap<HashAlgorithm, &str> = [
615            (HashAlgorithm::SHA1, "7945E3DA269C25C04F9EF435A5C0F25D9662C771"),
616            (HashAlgorithm::SHA512, "DDE60DB05C3958AF1E576CD006A7F3D2C343DD8C\
617                                     8DECE789A15D148DF90E6E0D1454DE734F834350\
618                                     2CA93759F22C8F6221BE35B6BDE9728BD12D2891\
619                                     22437CB1"),
620        ].iter().cloned().collect();
621
622        let reader
623            = buffered_reader::Generic::with_cookie(
624                std::io::Cursor::new(crate::tests::manifesto()),
625                None, Default::default());
626        let result =
627            hash_buffered_reader(
628                reader,
629                &expected.keys().cloned()
630                    .map(|v| HashingMode::Binary(vec![], v)).
631                    collect::<Vec<_>>())
632            .unwrap();
633
634        for mut mode in result.into_iter() {
635            let hash = mode.as_mut();
636            let algo = hash.algo();
637            let mut digest = vec![0u8; hash.digest_size()];
638            let _ = hash.digest(&mut digest);
639
640            assert_eq!(*expected.get(&algo).unwrap(),
641                       &crate::fmt::to_hex(&digest[..], false));
642        }
643    }
644}