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}
112impl Display for SignatureBox<'_> {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        let mut s = String::new();
115        s.push_str("untrusted comment: ");
116        if let Some(c) = self.untrusted_comment {
117            s.push_str(c);
118        }
119        s.push('\n');
120        let encoder = base64::engine::general_purpose::STANDARD;
121        let mut sig_format = vec![];
122        sig_format.extend_from_slice(&self.signature.sig_alg);
123        sig_format.extend_from_slice(&self.signature.key_id);
124        sig_format.extend_from_slice(&self.signature.sig.to_bytes());
125        let sig = encoder.encode(&sig_format);
126        s.push_str(&sig);
127        s.push('\n');
128        s.push_str("trusted comment: ");
129        if let Some(c) = self.trusted_comment {
130            s.push_str(c);
131        }
132        s.push('\n');
133        let global_sig = encoder.encode(self.signature.global_sig.to_bytes());
134        s.push_str(&global_sig);
135        s.push('\n');
136        write!(f, "{}", s)
137    }
138}
139
140impl<'s> SignatureBox<'s> {
141    pub(crate) fn new(
142        untrusted_comment: Option<&'s str>,
143        trusted_comment: Option<&'s str>,
144        signature: Signature,
145    ) -> Self {
146        Self {
147            untrusted_comment,
148            trusted_comment,
149            signature,
150        }
151    }
152    pub fn is_prehashed(&self) -> bool {
153        self.signature.sig_alg == SIGALG_PREHASHED
154    }
155    pub fn untrusted_comment(&self) -> Option<&'s str> {
156        self.untrusted_comment
157    }
158    pub fn trusted_comment(&self) -> Option<&'s str> {
159        self.trusted_comment
160    }
161    pub fn key_id(&self) -> &[u8; KID_SIZE] {
162        &self.signature.key_id
163    }
164    #[allow(clippy::should_implement_trait)]
165    pub fn from_str(s: &str) -> Result<SignatureBox> {
166        parse_signature(s)
167    }
168}
169#[derive(Debug, Clone)]
170pub(crate) struct Signature {
171    pub sig_alg: [u8; ALG_SIZE],
172    pub key_id: [u8; KID_SIZE],
173    pub sig: ed25519::Signature,
174    pub global_sig: ed25519::Signature,
175}
176impl Signature {
177    pub fn new(
178        sig_alg: [u8; ALG_SIZE],
179        key_id: [u8; KID_SIZE],
180        sig: ed25519::Signature,
181        global_sig: ed25519::Signature,
182    ) -> Self {
183        Self {
184            sig_alg,
185            key_id,
186            sig,
187            global_sig,
188        }
189    }
190}