libsignal_protocol/
fingerprint.rs

1//
2// Copyright 2020 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6use std::fmt;
7use std::fmt::Write;
8
9use prost::Message;
10use sha2::Sha512;
11use sha2::digest::Digest;
12use subtle::ConstantTimeEq;
13
14use crate::{IdentityKey, proto};
15
16#[derive(Debug, displaydoc::Display)]
17pub enum Error {
18    /// fingerprint version number mismatch them {theirs} us {ours}
19    VersionMismatch { theirs: u32, ours: u32 },
20    /// fingerprint parsing error: {0}
21    ParsingError(&'static str),
22    /// Invalid fingerprint iterations {0}
23    InvalidIterationCount(u32),
24}
25
26#[derive(Debug, Clone)]
27pub struct DisplayableFingerprint {
28    local: String,
29    remote: String,
30}
31
32impl fmt::Display for DisplayableFingerprint {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        if self.local < self.remote {
35            write!(f, "{}{}", self.local, self.remote)
36        } else {
37            write!(f, "{}{}", self.remote, self.local)
38        }
39    }
40}
41
42fn get_encoded_string(fprint: &[u8]) -> Result<String, Error> {
43    if fprint.len() < 30 {
44        return Err(Error::ParsingError(
45            "DisplayableFingerprint created with short encoding",
46        ));
47    }
48
49    fn read5_mod_100k(fprint: &[u8]) -> u64 {
50        assert_eq!(fprint.len(), 5);
51        let x = fprint.iter().fold(0u64, |acc, &x| (acc << 8) | (x as u64));
52        x % 100_000
53    }
54
55    let s = fprint.chunks_exact(5).take(6).map(read5_mod_100k).fold(
56        String::with_capacity(5 * 6),
57        |mut s, n| {
58            write!(s, "{n:05}").expect("can always write to a String");
59            s
60        },
61    );
62
63    Ok(s)
64}
65
66impl DisplayableFingerprint {
67    pub fn new(local: &[u8], remote: &[u8]) -> Result<Self, Error> {
68        Ok(Self {
69            local: get_encoded_string(local)?,
70            remote: get_encoded_string(remote)?,
71        })
72    }
73}
74
75#[derive(Debug, Clone)]
76pub struct ScannableFingerprint {
77    version: u32,
78    local_fingerprint: Vec<u8>,
79    remote_fingerprint: Vec<u8>,
80}
81
82impl ScannableFingerprint {
83    fn new(version: u32, local_fprint: &[u8], remote_fprint: &[u8]) -> Self {
84        Self {
85            version,
86            local_fingerprint: local_fprint[..32].to_vec(),
87            remote_fingerprint: remote_fprint[..32].to_vec(),
88        }
89    }
90
91    pub fn deserialize(protobuf: &[u8]) -> Result<Self, Error> {
92        let fingerprint = proto::fingerprint::CombinedFingerprints::decode(protobuf)
93            .map_err(|_| Error::ParsingError("failed to decode protobuf"))?;
94
95        Ok(Self {
96            version: fingerprint
97                .version
98                .ok_or(Error::ParsingError("missing version"))?,
99            local_fingerprint: fingerprint
100                .local_fingerprint
101                .and_then(|m| m.content)
102                .ok_or(Error::ParsingError("missing local fingerprint"))?,
103            remote_fingerprint: fingerprint
104                .remote_fingerprint
105                .and_then(|m| m.content)
106                .ok_or(Error::ParsingError("missing remote fingerprint"))?,
107        })
108    }
109
110    pub fn serialize(&self) -> Result<Vec<u8>, Error> {
111        let combined_fingerprints = proto::fingerprint::CombinedFingerprints {
112            version: Some(self.version),
113            local_fingerprint: Some(proto::fingerprint::LogicalFingerprint {
114                content: Some(self.local_fingerprint.to_owned()),
115            }),
116            remote_fingerprint: Some(proto::fingerprint::LogicalFingerprint {
117                content: Some(self.remote_fingerprint.to_owned()),
118            }),
119        };
120
121        Ok(combined_fingerprints.encode_to_vec())
122    }
123
124    pub fn compare(&self, combined: &[u8]) -> Result<bool, Error> {
125        let combined = proto::fingerprint::CombinedFingerprints::decode(combined)
126            .map_err(|_| Error::ParsingError("failed to decode their protobuf"))?;
127
128        let their_version = combined.version.unwrap_or(0);
129
130        if their_version != self.version {
131            return Err(Error::VersionMismatch {
132                theirs: their_version,
133                ours: self.version,
134            });
135        }
136
137        let same1 = combined
138            .local_fingerprint
139            .as_ref()
140            .and_then(|m| m.content.as_ref())
141            .ok_or(Error::ParsingError("missing their local fingerprint"))?
142            .ct_eq(&self.remote_fingerprint);
143        let same2 = combined
144            .remote_fingerprint
145            .as_ref()
146            .and_then(|m| m.content.as_ref())
147            .ok_or(Error::ParsingError("missing their remote fingerprint"))?
148            .ct_eq(&self.local_fingerprint);
149
150        Ok(same1.into() && same2.into())
151    }
152}
153
154#[derive(Debug, Clone)]
155pub struct Fingerprint {
156    pub display: DisplayableFingerprint,
157    pub scannable: ScannableFingerprint,
158}
159
160impl Fingerprint {
161    fn get_fingerprint(
162        iterations: u32,
163        local_id: &[u8],
164        local_key: &IdentityKey,
165    ) -> Result<Vec<u8>, Error> {
166        if iterations <= 1 || iterations > 1000000 {
167            return Err(Error::InvalidIterationCount(iterations));
168        }
169
170        let fingerprint_version = [0u8, 0u8]; // 0x0000
171        let key_bytes = local_key.serialize();
172
173        let mut sha512 = Sha512::new();
174
175        // iteration=0
176        // Explicitly pass a slice to avoid generating multiple versions of update().
177        sha512.update(&fingerprint_version[..]);
178        sha512.update(&key_bytes);
179        sha512.update(local_id);
180        sha512.update(&key_bytes);
181        let mut buf = sha512.finalize();
182
183        for _i in 1..iterations {
184            let mut sha512 = Sha512::new();
185            // Explicitly pass a slice to avoid generating multiple versions of update().
186            sha512.update(&buf[..]);
187            sha512.update(&key_bytes);
188            buf = sha512.finalize();
189        }
190
191        Ok(buf.to_vec())
192    }
193
194    pub fn new(
195        version: u32,
196        iterations: u32,
197        local_id: &[u8],
198        local_key: &IdentityKey,
199        remote_id: &[u8],
200        remote_key: &IdentityKey,
201    ) -> Result<Fingerprint, Error> {
202        let local_fingerprint = Fingerprint::get_fingerprint(iterations, local_id, local_key)?;
203        let remote_fingerprint = Fingerprint::get_fingerprint(iterations, remote_id, remote_key)?;
204
205        Ok(Fingerprint {
206            display: DisplayableFingerprint::new(&local_fingerprint, &remote_fingerprint)?,
207            scannable: ScannableFingerprint::new(version, &local_fingerprint, &remote_fingerprint),
208        })
209    }
210
211    pub fn display_string(&self) -> Result<String, Error> {
212        Ok(self.display.to_string())
213    }
214}
215
216#[cfg(test)]
217mod test {
218    use const_str::hex;
219    use rand::TryRngCore as _;
220
221    use super::*;
222
223    const ALICE_IDENTITY: &[u8] =
224        &hex!("0506863bc66d02b40d27b8d49ca7c09e9239236f9d7d25d6fcca5ce13c7064d868");
225    const BOB_IDENTITY: &[u8] =
226        &hex!("05f781b6fb32fed9ba1cf2de978d4d5da28dc34046ae814402b5c0dbd96fda907b");
227
228    const DISPLAYABLE_FINGERPRINT_V1: &str =
229        "300354477692869396892869876765458257569162576843440918079131";
230    const ALICE_SCANNABLE_FINGERPRINT_V1: &str = "080112220a201e301a0353dce3dbe7684cb8336e85136cdc0ee96219494ada305d62a7bd61df1a220a20d62cbf73a11592015b6b9f1682ac306fea3aaf3885b84d12bca631e9d4fb3a4d";
231    const BOB_SCANNABLE_FINGERPRINT_V1: &str = "080112220a20d62cbf73a11592015b6b9f1682ac306fea3aaf3885b84d12bca631e9d4fb3a4d1a220a201e301a0353dce3dbe7684cb8336e85136cdc0ee96219494ada305d62a7bd61df";
232
233    const ALICE_SCANNABLE_FINGERPRINT_V2: &str = "080212220a201e301a0353dce3dbe7684cb8336e85136cdc0ee96219494ada305d62a7bd61df1a220a20d62cbf73a11592015b6b9f1682ac306fea3aaf3885b84d12bca631e9d4fb3a4d";
234    const BOB_SCANNABLE_FINGERPRINT_V2: &str = "080212220a20d62cbf73a11592015b6b9f1682ac306fea3aaf3885b84d12bca631e9d4fb3a4d1a220a201e301a0353dce3dbe7684cb8336e85136cdc0ee96219494ada305d62a7bd61df";
235
236    const ALICE_STABLE_ID: &str = "+14152222222";
237    const BOB_STABLE_ID: &str = "+14153333333";
238
239    #[test]
240    fn fingerprint_encodings() -> Result<(), Error> {
241        let l = vec![0x12; 32];
242        let r = vec![0xBA; 32];
243
244        let fprint2 = ScannableFingerprint::new(2, &l, &r);
245        let proto2 = fprint2.serialize()?;
246
247        let expected2_encoding =
248            "080212220a20".to_owned() + &"12".repeat(32) + "1a220a20" + &"ba".repeat(32);
249        assert_eq!(hex::encode(proto2), expected2_encoding);
250
251        Ok(())
252    }
253
254    #[test]
255    fn fingerprint_test_v1() -> Result<(), Error> {
256        // testVectorsVersion1 in Java
257
258        let a_key = IdentityKey::decode(ALICE_IDENTITY).expect("valid");
259        let b_key = IdentityKey::decode(BOB_IDENTITY).expect("valid");
260
261        let version = 1;
262        let iterations = 5200;
263
264        let a_fprint = Fingerprint::new(
265            version,
266            iterations,
267            ALICE_STABLE_ID.as_bytes(),
268            &a_key,
269            BOB_STABLE_ID.as_bytes(),
270            &b_key,
271        )?;
272
273        let b_fprint = Fingerprint::new(
274            version,
275            iterations,
276            BOB_STABLE_ID.as_bytes(),
277            &b_key,
278            ALICE_STABLE_ID.as_bytes(),
279            &a_key,
280        )?;
281
282        assert_eq!(
283            hex::encode(a_fprint.scannable.serialize()?),
284            ALICE_SCANNABLE_FINGERPRINT_V1
285        );
286        assert_eq!(
287            hex::encode(b_fprint.scannable.serialize()?),
288            BOB_SCANNABLE_FINGERPRINT_V1
289        );
290
291        assert_eq!(format!("{}", a_fprint.display), DISPLAYABLE_FINGERPRINT_V1);
292        assert_eq!(format!("{}", b_fprint.display), DISPLAYABLE_FINGERPRINT_V1);
293
294        assert_eq!(
295            hex::encode(a_fprint.scannable.serialize()?),
296            ALICE_SCANNABLE_FINGERPRINT_V1
297        );
298        assert_eq!(
299            hex::encode(b_fprint.scannable.serialize()?),
300            BOB_SCANNABLE_FINGERPRINT_V1
301        );
302
303        Ok(())
304    }
305
306    #[test]
307    fn fingerprint_test_v2() -> Result<(), Error> {
308        // testVectorsVersion2 in Java
309
310        let a_key = IdentityKey::decode(ALICE_IDENTITY).expect("valid");
311        let b_key = IdentityKey::decode(BOB_IDENTITY).expect("valid");
312
313        let version = 2;
314        let iterations = 5200;
315
316        let a_fprint = Fingerprint::new(
317            version,
318            iterations,
319            ALICE_STABLE_ID.as_bytes(),
320            &a_key,
321            BOB_STABLE_ID.as_bytes(),
322            &b_key,
323        )?;
324
325        let b_fprint = Fingerprint::new(
326            version,
327            iterations,
328            BOB_STABLE_ID.as_bytes(),
329            &b_key,
330            ALICE_STABLE_ID.as_bytes(),
331            &a_key,
332        )?;
333
334        assert_eq!(
335            hex::encode(a_fprint.scannable.serialize()?),
336            ALICE_SCANNABLE_FINGERPRINT_V2
337        );
338        assert_eq!(
339            hex::encode(b_fprint.scannable.serialize()?),
340            BOB_SCANNABLE_FINGERPRINT_V2
341        );
342
343        // unchanged vs v1
344        assert_eq!(format!("{}", a_fprint.display), DISPLAYABLE_FINGERPRINT_V1);
345        assert_eq!(format!("{}", b_fprint.display), DISPLAYABLE_FINGERPRINT_V1);
346
347        assert_eq!(
348            hex::encode(a_fprint.scannable.serialize()?),
349            ALICE_SCANNABLE_FINGERPRINT_V2
350        );
351        assert_eq!(
352            hex::encode(b_fprint.scannable.serialize()?),
353            BOB_SCANNABLE_FINGERPRINT_V2
354        );
355
356        Ok(())
357    }
358
359    #[test]
360    fn fingerprint_matching_identifiers() -> Result<(), Error> {
361        // testMatchingFingerprints
362
363        use rand::rngs::OsRng;
364
365        use crate::IdentityKeyPair;
366
367        let a_key_pair = IdentityKeyPair::generate(&mut OsRng.unwrap_err());
368        let b_key_pair = IdentityKeyPair::generate(&mut OsRng.unwrap_err());
369
370        let a_key = a_key_pair.identity_key();
371        let b_key = b_key_pair.identity_key();
372
373        let version = 1;
374        let iterations = 1024;
375
376        let a_fprint = Fingerprint::new(
377            version,
378            iterations,
379            ALICE_STABLE_ID.as_bytes(),
380            a_key,
381            BOB_STABLE_ID.as_bytes(),
382            b_key,
383        )?;
384
385        let b_fprint = Fingerprint::new(
386            version,
387            iterations,
388            BOB_STABLE_ID.as_bytes(),
389            b_key,
390            ALICE_STABLE_ID.as_bytes(),
391            a_key,
392        )?;
393
394        assert_eq!(
395            format!("{}", a_fprint.display),
396            format!("{}", b_fprint.display)
397        );
398        assert_eq!(format!("{}", a_fprint.display).len(), 60);
399
400        assert!(
401            a_fprint
402                .scannable
403                .compare(&b_fprint.scannable.serialize()?)?
404        );
405        assert!(
406            b_fprint
407                .scannable
408                .compare(&a_fprint.scannable.serialize()?)?
409        );
410
411        // Java is missing this test
412        assert!(
413            !a_fprint
414                .scannable
415                .compare(&a_fprint.scannable.serialize()?)?
416        );
417        assert!(
418            !b_fprint
419                .scannable
420                .compare(&b_fprint.scannable.serialize()?)?
421        );
422
423        Ok(())
424    }
425
426    #[test]
427    fn fingerprint_mismatching_fingerprints() -> Result<(), Error> {
428        use rand::rngs::OsRng;
429
430        use crate::IdentityKeyPair;
431
432        let mut rng = OsRng.unwrap_err();
433        let a_key_pair = IdentityKeyPair::generate(&mut rng);
434        let b_key_pair = IdentityKeyPair::generate(&mut rng);
435        let m_key_pair = IdentityKeyPair::generate(&mut rng); // mitm
436
437        let a_key = a_key_pair.identity_key();
438        let b_key = b_key_pair.identity_key();
439        let m_key = m_key_pair.identity_key();
440
441        let version = 1;
442        let iterations = 1024;
443
444        let a_fprint = Fingerprint::new(
445            version,
446            iterations,
447            ALICE_STABLE_ID.as_bytes(),
448            a_key,
449            BOB_STABLE_ID.as_bytes(),
450            m_key,
451        )?;
452
453        let b_fprint = Fingerprint::new(
454            version,
455            iterations,
456            BOB_STABLE_ID.as_bytes(),
457            b_key,
458            ALICE_STABLE_ID.as_bytes(),
459            a_key,
460        )?;
461
462        assert_ne!(
463            format!("{}", a_fprint.display),
464            format!("{}", b_fprint.display)
465        );
466
467        assert!(
468            !a_fprint
469                .scannable
470                .compare(&b_fprint.scannable.serialize()?)?
471        );
472        assert!(
473            !b_fprint
474                .scannable
475                .compare(&a_fprint.scannable.serialize()?)?
476        );
477
478        Ok(())
479    }
480
481    #[test]
482    fn fingerprint_mismatching_identifiers() -> Result<(), Error> {
483        use rand::rngs::OsRng;
484
485        use crate::IdentityKeyPair;
486
487        let mut rng = OsRng.unwrap_err();
488        let a_key_pair = IdentityKeyPair::generate(&mut rng);
489        let b_key_pair = IdentityKeyPair::generate(&mut rng);
490
491        let a_key = a_key_pair.identity_key();
492        let b_key = b_key_pair.identity_key();
493
494        let version = 1;
495        let iterations = 1024;
496
497        let a_fprint = Fingerprint::new(
498            version,
499            iterations,
500            "+141512222222".as_bytes(),
501            a_key,
502            BOB_STABLE_ID.as_bytes(),
503            b_key,
504        )?;
505
506        let b_fprint = Fingerprint::new(
507            version,
508            iterations,
509            BOB_STABLE_ID.as_bytes(),
510            b_key,
511            ALICE_STABLE_ID.as_bytes(),
512            a_key,
513        )?;
514
515        assert_ne!(
516            format!("{}", a_fprint.display),
517            format!("{}", b_fprint.display)
518        );
519
520        assert!(
521            !a_fprint
522                .scannable
523                .compare(&b_fprint.scannable.serialize()?)?
524        );
525        assert!(
526            !b_fprint
527                .scannable
528                .compare(&a_fprint.scannable.serialize()?)?
529        );
530
531        Ok(())
532    }
533
534    #[test]
535    fn fingerprint_mismatching_versions() -> Result<(), Error> {
536        let a_key = IdentityKey::decode(ALICE_IDENTITY).expect("valid");
537        let b_key = IdentityKey::decode(BOB_IDENTITY).expect("valid");
538
539        let iterations = 5200;
540
541        let a_fprint_v1 = Fingerprint::new(
542            1,
543            iterations,
544            ALICE_STABLE_ID.as_bytes(),
545            &a_key,
546            BOB_STABLE_ID.as_bytes(),
547            &b_key,
548        )?;
549
550        let a_fprint_v2 = Fingerprint::new(
551            2,
552            iterations,
553            BOB_STABLE_ID.as_bytes(),
554            &b_key,
555            ALICE_STABLE_ID.as_bytes(),
556            &a_key,
557        )?;
558
559        // Display fingerprint doesn't change
560        assert_eq!(
561            format!("{}", a_fprint_v1.display),
562            format!("{}", a_fprint_v2.display)
563        );
564
565        // Scannable fingerprint does
566        assert_ne!(
567            hex::encode(a_fprint_v1.scannable.serialize()?),
568            hex::encode(a_fprint_v2.scannable.serialize()?)
569        );
570
571        Ok(())
572    }
573}