Skip to main content

mini_sign/
signature.rs

1use std::fmt::Display;
2use std::vec;
3
4use base64::Engine;
5use ed25519_dalek::ed25519;
6
7use crate::errors::Result;
8use crate::{SError, ALG_SIZE, KID_SIZE, SIGALG_PREHASHED, SIG_SIZE};
9/// A `SignatureBox` represents a minisign signature.
10///
11/// also can be output to a string and parse from a str.
12///
13/// # Security
14///
15/// This does not mean trusted_comment is verified.
16/// must verify the signature by `PublicKeyBox`.
17#[derive(Debug, Clone)]
18pub struct SignatureBox<'s> {
19    pub(crate) untrusted_comment: Option<&'s str>,
20    pub(crate) trusted_comment: Option<&'s str>,
21    pub(crate) signature: Signature,
22}
23fn parse_signature(s: &'_ str) -> Result<SignatureBox<'_>> {
24    let mut lines = s.lines();
25    let untrusted_comment = if let Some(c) = lines.next() {
26        if let Some(uc) = c.strip_prefix("untrusted comment: ") {
27            Some(uc)
28        } else {
29            return Err(SError::new(
30                crate::ErrorKind::SignatureError,
31                "missing untrusted comment",
32            ));
33        }
34    } else {
35        None
36    };
37    let sig = lines
38        .next()
39        .ok_or_else(|| SError::new(crate::ErrorKind::SignatureError, "missing signature"))?;
40    let decoder = base64::engine::general_purpose::STANDARD;
41    let sig_format = decoder
42        .decode(sig.as_bytes())
43        .map_err(|e| SError::new(crate::ErrorKind::SignatureError, e))?;
44    if sig_format.len() != ALG_SIZE + KID_SIZE + SIG_SIZE {
45        return Err(SError::new(
46            crate::ErrorKind::SignatureError,
47            "invalid signature length",
48        ));
49    }
50    let sig_alg = &sig_format[..ALG_SIZE];
51    let key_id = &sig_format[ALG_SIZE..ALG_SIZE + KID_SIZE];
52    let sig = &sig_format[ALG_SIZE + KID_SIZE..];
53    let trusted_comment = if let Some(c) = lines.next() {
54        if let Some(tc) = c.strip_prefix("trusted comment: ") {
55            Some(tc)
56        } else {
57            return Err(SError::new(
58                crate::ErrorKind::SignatureError,
59                "missing trusted comment",
60            ));
61        }
62    } else {
63        return Err(SError::new(
64            crate::ErrorKind::SignatureError,
65            "missing trusted comment",
66        ));
67    };
68    let global_sig = lines
69        .next()
70        .ok_or_else(|| SError::new(crate::ErrorKind::SignatureError, "missing global signature"))?;
71    let global_sig_format = decoder
72        .decode(global_sig.as_bytes())
73        .map_err(|e| SError::new(crate::ErrorKind::SignatureError, e))?;
74    if global_sig_format.len() != 64 {
75        return Err(SError::new(
76            crate::ErrorKind::SignatureError,
77            "invalid global signature length",
78        ));
79    }
80    Ok(SignatureBox::new(
81        untrusted_comment,
82        trusted_comment,
83        Signature::new(
84            sig_alg.try_into().unwrap(),
85            key_id.try_into().unwrap(),
86            sig.try_into().unwrap(),
87            ed25519::Signature::from_bytes(&global_sig_format.try_into().unwrap()),
88        ),
89    ))
90}
91
92#[cfg(test)]
93#[test]
94fn test_parse_signature() {
95    use crate::{sign, KeyPairBox};
96
97    let password = b"password";
98    let k = KeyPairBox::generate(Some(password), None, None).unwrap();
99    let file = sign(
100        Some(&k.public_key_box),
101        &k.secret_key_box,
102        Some(password),
103        "test".as_bytes(),
104        Some("trusted comment"),
105        Some("untrusted comment"),
106    )
107    .unwrap()
108    .to_string();
109    let sig = parse_signature(&file).unwrap();
110    assert_eq!(file, sig.to_string());
111}
112fn sanitize_comment(comment: Option<&str>) -> Option<String> {
113    comment.map(|c| {
114        c.chars()
115            .filter(|c| !c.is_control())
116            .collect::<String>()
117    })
118}
119impl Display for SignatureBox<'_> {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        let mut s = String::new();
122        s.push_str("untrusted comment: ");
123        if let Some(c) = sanitize_comment(self.untrusted_comment) {
124            s.push_str(&c);
125        }
126        s.push('\n');
127        let encoder = base64::engine::general_purpose::STANDARD;
128        let mut sig_format = vec![];
129        sig_format.extend_from_slice(&self.signature.sig_alg);
130        sig_format.extend_from_slice(&self.signature.key_id);
131        sig_format.extend_from_slice(&self.signature.sig.to_bytes());
132        let sig = encoder.encode(&sig_format);
133        s.push_str(&sig);
134        s.push('\n');
135        s.push_str("trusted comment: ");
136        if let Some(c) = sanitize_comment(self.trusted_comment) {
137            s.push_str(&c);
138        }
139        s.push('\n');
140        let global_sig = encoder.encode(self.signature.global_sig.to_bytes());
141        s.push_str(&global_sig);
142        s.push('\n');
143        write!(f, "{}", s)
144    }
145}
146
147impl<'s> SignatureBox<'s> {
148    pub(crate) fn new(
149        untrusted_comment: Option<&'s str>,
150        trusted_comment: Option<&'s str>,
151        signature: Signature,
152    ) -> Self {
153        Self {
154            untrusted_comment,
155            trusted_comment,
156            signature,
157        }
158    }
159    pub fn is_prehashed(&self) -> bool {
160        self.signature.sig_alg == SIGALG_PREHASHED
161    }
162    pub fn untrusted_comment(&self) -> Option<&'s str> {
163        self.untrusted_comment
164    }
165    pub fn trusted_comment(&self) -> Option<&'s str> {
166        self.trusted_comment
167    }
168    pub fn key_id(&self) -> &[u8; KID_SIZE] {
169        &self.signature.key_id
170    }
171    #[allow(clippy::should_implement_trait)]
172    pub fn from_str(s: &str) -> Result<SignatureBox<'_>> {
173        parse_signature(s)
174    }
175}
176#[derive(Debug, Clone)]
177pub(crate) struct Signature {
178    pub sig_alg: [u8; ALG_SIZE],
179    pub key_id: [u8; KID_SIZE],
180    pub sig: ed25519::Signature,
181    pub global_sig: ed25519::Signature,
182}
183impl Signature {
184    pub fn new(
185        sig_alg: [u8; ALG_SIZE],
186        key_id: [u8; KID_SIZE],
187        sig: ed25519::Signature,
188        global_sig: ed25519::Signature,
189    ) -> Self {
190        Self {
191            sig_alg,
192            key_id,
193            sig,
194            global_sig,
195        }
196    }
197}