1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use crate::crypto::hash::Digest;
use crate::Result;

pub(crate) fn build() -> sha1collisiondetection::Sha1CD {
    sha1collisiondetection::Builder::default()
        .detect_collisions(true)
        .use_ubc(true)
        .safe_hash(true)
        .build()
}

impl Digest for sha1collisiondetection::Sha1CD {
    fn algo(&self) -> crate::types::HashAlgorithm {
        crate::types::HashAlgorithm::SHA1
    }

    fn digest_size(&self) -> usize {
        20
    }

    fn update(&mut self, data: &[u8]) {
        sha1collisiondetection::Sha1CD::update(self, data);
    }

    fn digest(&mut self, digest: &mut [u8]) -> Result<()> {
        let mut d = sha1collisiondetection::Output::default();
        let r = self.finalize_into_dirty_cd(&mut d);
        self.reset();
        let l = digest.len().min(d.len());
        &mut digest[..l].copy_from_slice(&d[..l]);
        r.map_err(Into::into)
    }
}

#[cfg(test)]
mod test {
    use crate::*;
    use crate::parse::{Parse, stream::*};
    use crate::policy::StandardPolicy;

    /// Test vector from the "SHA-1 is a Shambles" paper.
    ///
    /// The scenario is the following.  Bob obtains a certification
    /// from a CA, and transfers it to a key claiming to belong to
    /// Alice.  Now the CA certifies an illegitimate binding to
    /// Alice's userid.
    #[test]
    fn shambles() -> Result<()> {
        let alice =
            PacketPile::from_bytes(crate::tests::key("sha-mbles.alice.asc"))?;
        let bob =
            PacketPile::from_bytes(crate::tests::key("sha-mbles.bob.asc"))?;
        let ca_keyid: KeyID = "AFBB 1FED 6951 A956".parse()?;

        assert_eq!(alice.children().count(), 4);
        assert_eq!(bob.children().count(), 7);

        let alice_sha1_fingerprint: Fingerprint =
            "43CD 5C5B 04FF 5742 FA14  1ABC A9D7 55A9 6354 8C78".parse()?;
        let bob_sha1_fingerprint: Fingerprint =
            "C6BF E2FC BBE5 1A89 2BEB  7798 1233 D4CC 61DB D9C4".parse()?;

        let alice_sha1cd_fingerprint: Fingerprint =
            "4D84 B08A A181 21DB D79E  EA05 9CD0 8D5B 1680 87E2".parse()?;
        let bob_sha1cd_fingerprint: Fingerprint =
            "6434 B04B 4648 BA41 15BD  C5C2 B67A DB26 6F74 DF89".parse()?;

        // The illegitimate certification is on Bob's user attribute.
        assert_eq!(bob.path_ref(&[6]).unwrap(), alice.path_ref(&[3]).unwrap());
        match bob.path_ref(&[6]).unwrap() {
            Packet::Signature(s) => {
                assert_eq!(s.issuers().nth(0).unwrap(), &ca_keyid);
            },
            o => panic!("unexpected packet: {:?}", o),
        }

        let alice = Cert::from_packets(alice.into_children())?;
        let bob = Cert::from_packets(bob.into_children())?;

        // Check mitigations.  First, the illegitimate certification
        // should be discarded.
        assert_eq!(alice.bad_signatures().count(), 1);
        // Bob's userid also got certified, hence there are two bad
        // signatures.
        assert_eq!(bob.bad_signatures().count(), 2);

        // The mitigation also changes the identities of the keys
        // containing the collision attack.  This is a good thing,
        // because we cannot trust SHA-1 to discriminate keys
        // containing attacks.
        assert!(alice.fingerprint() != alice_sha1_fingerprint);
        assert_eq!(alice.fingerprint(), alice_sha1cd_fingerprint);
        assert!(bob.fingerprint() != bob_sha1_fingerprint);
        assert_eq!(bob.fingerprint(), bob_sha1cd_fingerprint);
        Ok(())
    }

    /// Test vector from the paper "The first collision for full SHA-1".
    #[test]
    fn shattered() -> Result<()> {
        let cert =
            Cert::from_bytes(crate::tests::key("testy-new.pgp"))?;
        let shattered_1 = crate::tests::message("shattered-1.pdf");
        let shattered_1_sig = crate::tests::message("shattered-1.pdf.sig");
        let shattered_2 = crate::tests::message("shattered-2.pdf");
        let shattered_2_sig = crate::tests::message("shattered-2.pdf.sig");

        let mut p = StandardPolicy::new();
        p.accept_hash(types::HashAlgorithm::SHA1);

        // This fetches keys and computes the validity of the verification.
        struct Helper(Cert);
        impl VerificationHelper for Helper {
            fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result<Vec<Cert>> {
                Ok(vec![self.0.clone()])
            }
            fn check(&mut self, structure: MessageStructure) -> Result<()> {
                if let MessageLayer::SignatureGroup { results } =
                    structure.into_iter().nth(0).unwrap()
                {
                    assert_eq!(results.len(), 1);
                    assert!(results[0].is_err());
                } else {
                    unreachable!()
                }
                Ok(())
            }
        }

        let h = Helper(cert.clone());
        let mut v = DetachedVerifierBuilder::from_bytes(shattered_1_sig)?
            .with_policy(&p, None, h)?;
        v.verify_bytes(shattered_1)?;

        let h = Helper(cert);
        let mut v = DetachedVerifierBuilder::from_bytes(shattered_2_sig)?
            .with_policy(&p, None, h)?;
        v.verify_bytes(shattered_2)?;

        Ok(())
    }
}