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
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>

use std::{
    collections::BTreeMap,
    convert::TryFrom,
    iter::FromIterator,
    ops::{Deref, DerefMut},
};

use crypto::{ssh, PublicKey};
use git_ext::commit::{
    headers::Signature::{Pgp, Ssh},
    Commit,
};

pub use ssh::ExtendedSignature;
pub mod error;

// FIXME(kim): This should really be a HashMap with a no-op Hasher -- PublicKey
// collisions are catastrophic
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Signatures(BTreeMap<PublicKey, crypto::Signature>);

impl Deref for Signatures {
    type Target = BTreeMap<PublicKey, crypto::Signature>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Signatures {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl From<ExtendedSignature> for Signatures {
    fn from(ExtendedSignature { key, sig }: ExtendedSignature) -> Self {
        let mut map = BTreeMap::new();
        map.insert(key, sig);
        map.into()
    }
}

impl From<BTreeMap<PublicKey, crypto::Signature>> for Signatures {
    fn from(map: BTreeMap<PublicKey, crypto::Signature>) -> Self {
        Self(map)
    }
}

impl From<Signatures> for BTreeMap<PublicKey, crypto::Signature> {
    fn from(s: Signatures) -> Self {
        s.0
    }
}

impl TryFrom<&Commit> for Signatures {
    type Error = error::Signatures;

    fn try_from(value: &Commit) -> Result<Self, Self::Error> {
        value
            .signatures()
            .filter_map(|signature| {
                match signature {
                    // Skip PGP signatures
                    Pgp(_) => None,
                    Ssh(pem) => Some(
                        ExtendedSignature::from_pem(pem.as_bytes())
                            .map_err(error::Signatures::from),
                    ),
                }
            })
            .map(|r| r.map(|es| (es.key, es.sig)))
            .collect::<Result<_, _>>()
    }
}

impl FromIterator<(PublicKey, crypto::Signature)> for Signatures {
    fn from_iter<T>(iter: T) -> Self
    where
        T: IntoIterator<Item = (PublicKey, crypto::Signature)>,
    {
        Self(BTreeMap::from_iter(iter))
    }
}

impl IntoIterator for Signatures {
    type Item = (PublicKey, crypto::Signature);
    type IntoIter = <BTreeMap<PublicKey, crypto::Signature> as IntoIterator>::IntoIter;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

impl Extend<ExtendedSignature> for Signatures {
    fn extend<T>(&mut self, iter: T)
    where
        T: IntoIterator<Item = ExtendedSignature>,
    {
        for ExtendedSignature { key, sig } in iter {
            self.insert(key, sig);
        }
    }
}

impl Extend<(PublicKey, crypto::Signature)> for Signatures {
    fn extend<T>(&mut self, iter: T)
    where
        T: IntoIterator<Item = (PublicKey, crypto::Signature)>,
    {
        for (key, sig) in iter {
            self.insert(key, sig);
        }
    }
}