Skip to main content

mini_sign/
public_key.rs

1use std::{fmt::Display, io::Read};
2
3use crate::{
4    errors::Result, prehash, signature::Signature, util::validate_comment, ErrorKind, SError,
5    SignatureBox, ALG_SIZE, COMPONENT_SIZE, KEY_SIG_ALG, KID_SIZE,
6};
7use base64::Engine;
8use ed25519_dalek::ed25519::{self, ComponentBytes};
9/// A `PublicKeyBox` represents a minisign public key.
10///
11/// also can be output to a string and parse from a str.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct PublicKeyBox<'s> {
14    pub(crate) untrusted_comment: Option<&'s str>,
15    pub(crate) public_key: PublicKey,
16}
17
18impl<'s> PublicKeyBox<'s> {
19    pub(crate) fn new(untrusted_comment: Option<&'s str>, public_key: PublicKey) -> Result<Self> {
20        validate_comment(untrusted_comment, ErrorKind::PublicKey)?;
21        Ok(Self {
22            untrusted_comment,
23            public_key,
24        })
25    }
26    pub fn from_verifying_key(
27        key: ed25519_dalek::VerifyingKey,
28        key_id: &[u8; 8],
29        untrusted_comment: Option<&'s str>,
30    ) -> Result<Self> {
31        let pk = RawPk::new(key.to_bytes());
32        let public_key = PublicKey::new(KEY_SIG_ALG, *key_id, pk);
33        Self::new(untrusted_comment, public_key)
34    }
35    /// Parse a `PublicKeyBox` from str.
36    ///
37    /// as it store in a file.
38    #[allow(clippy::should_implement_trait)]
39    pub fn from_str(s: &'s str) -> Result<Self> {
40        parse_public_key(s)
41    }
42    /// Get the public key from a raw string,without untrusted comment.
43    /// only one line.
44    pub fn from_raw_str(s: &'s str) -> Result<Self> {
45        let public_key = s.trim();
46        let decoder = base64::engine::general_purpose::STANDARD;
47        let pk_format = decoder
48            .decode(public_key.as_bytes())
49            .map_err(|e| SError::new(crate::ErrorKind::PublicKey, e))?;
50        if pk_format.len() != ALG_SIZE + KID_SIZE + COMPONENT_SIZE {
51            return Err(SError::new(
52                crate::ErrorKind::PublicKey,
53                "invalid public key length",
54            ));
55        }
56        let pk_sig_alg = &pk_format[..ALG_SIZE];
57        let pk_key_id = &pk_format[ALG_SIZE..ALG_SIZE + KID_SIZE];
58        let pk_key = &pk_format[ALG_SIZE + KID_SIZE..];
59        let pk = RawPk::new(pk_key.try_into().unwrap());
60        let public_key = PublicKey::new(
61            pk_sig_alg.try_into().unwrap(),
62            pk_key_id.try_into().unwrap(),
63            pk,
64        );
65        PublicKeyBox::new(None, public_key)
66    }
67    /// Get the untrusted comment.
68    pub fn untrusted_comment(&self) -> Option<&'s str> {
69        self.untrusted_comment
70    }
71    pub(crate) fn verify_mini(
72        &self,
73        msg: &[u8],
74        sig: &Signature,
75        trusted_comment: Option<&str>,
76    ) -> Result<bool> {
77        if !(self.public_key.key.verify(msg, &sig.sig)?) {
78            return Err(SError::new(
79                crate::ErrorKind::PublicKey,
80                "verify sig failed",
81            ));
82        }
83        let mut global_data = vec![];
84        global_data.extend_from_slice(&sig.sig.to_bytes());
85        global_data.extend_from_slice(trusted_comment.unwrap_or("").as_bytes());
86        if !(self.public_key.key.verify(&global_data, &sig.global_sig)?) {
87            return Err(SError::new(
88                crate::ErrorKind::PublicKey,
89                "verify global sig failed",
90            ));
91        }
92        Ok(true)
93    }
94    pub(crate) fn self_verify(&self) -> Result<bool> {
95        if self.public_key.sig_alg != KEY_SIG_ALG {
96            return Err(SError::new(
97                crate::ErrorKind::PublicKey,
98                "invalid public key signature algorithm",
99            ));
100        }
101        Ok(true)
102    }
103    /// Get the key id of the public key.
104    pub fn key_id(&self) -> &[u8; 8] {
105        &self.public_key.key_id
106    }
107    /// Get the signature algorithm of the public key.
108    pub fn sig_alg(&self) -> &[u8; 2] {
109        &self.public_key.sig_alg
110    }
111    /// Verify a signature with the public key.
112    ///
113    /// # Arguments
114    /// * `signature_box` - The signature to verify
115    /// * `data_reader` - The data to verify
116    /// # Returns
117    /// A Result containing a boolean indicating whether the signature is valid
118    /// # Errors
119    /// * `ErrorKind::Io` - If there is an error reading the data
120    /// * `ErrorKind::PublicKey` - If the public key is invalid or not matching the signature
121    /// * `ErrorKind::PrehashedMismatch` - If the signature is not prehashed
122    pub fn verify<R>(&self, signature_box: &SignatureBox, mut data_reader: R) -> Result<bool>
123    where
124        R: Read,
125    {
126        let prehashed = prehash(&mut data_reader)?;
127        verify_prehashed(self, signature_box, &prehashed)
128    }
129}
130pub(crate) fn verify_prehashed(
131    pk: &PublicKeyBox,
132    signature_box: &SignatureBox,
133    prehashed: &[u8],
134) -> Result<bool> {
135    if !signature_box.is_prehashed() {
136        return Err(SError::new(
137            ErrorKind::PrehashedMismatch,
138            "SignatureBox is not prehashed",
139        ));
140    }
141    if !pk.self_verify()? {
142        return Err(SError::new(
143            ErrorKind::PublicKey,
144            "public key self verification failed",
145        ));
146    }
147    if pk.public_key.key_id != *signature_box.key_id() {
148        return Err(SError::new(
149            ErrorKind::PublicKey,
150            "public key key_id mismatch",
151        ));
152    }
153    pk.verify_mini(
154        prehashed,
155        &signature_box.signature,
156        signature_box.trusted_comment(),
157    )
158}
159
160impl Display for PublicKeyBox<'_> {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        let mut s = String::new();
163        s.push_str("untrusted comment: ");
164        if let Some(c) = self.untrusted_comment {
165            s.push_str(c);
166        }
167        s.push('\n');
168        let encoder = base64::engine::general_purpose::STANDARD;
169        let mut pk_format = vec![];
170        pk_format.extend_from_slice(&self.public_key.sig_alg);
171        pk_format.extend_from_slice(&self.public_key.key_id);
172        pk_format.extend_from_slice(&self.public_key.key.0);
173        let pk = encoder.encode(&pk_format);
174        s.push_str(&pk);
175        s.push('\n');
176        write!(f, "{}", s)
177    }
178}
179fn parse_raw_public_key(public_key: &str) -> Result<PublicKey> {
180    let decoder = base64::engine::general_purpose::STANDARD;
181    let pk_format = decoder
182        .decode(public_key.as_bytes())
183        .map_err(|e| SError::new(crate::ErrorKind::PublicKey, e))?;
184    if pk_format.len() != ALG_SIZE + KID_SIZE + COMPONENT_SIZE {
185        return Err(SError::new(
186            crate::ErrorKind::PublicKey,
187            "invalid public key length",
188        ));
189    }
190    let pk_sig_alg = &pk_format[..ALG_SIZE];
191    let pk_key_id = &pk_format[ALG_SIZE..ALG_SIZE + KID_SIZE];
192    let pk_key = &pk_format[ALG_SIZE + KID_SIZE..];
193    let pk = RawPk::new(pk_key.try_into().unwrap());
194    let public_key = PublicKey::new(
195        pk_sig_alg.try_into().unwrap(),
196        pk_key_id.try_into().unwrap(),
197        pk,
198    );
199    Ok(public_key)
200}
201fn parse_public_key(s: &str) -> Result<PublicKeyBox<'_>> {
202    let mut lines = s.lines();
203    let untrusted_comment = lines
204        .next()
205        .ok_or_else(|| SError::new(crate::ErrorKind::PublicKey, "empty public key"))?
206        .strip_prefix("untrusted comment: ")
207        .ok_or_else(|| SError::new(crate::ErrorKind::PublicKey, "missing untrusted comment"))?;
208    validate_comment(Some(untrusted_comment), ErrorKind::PublicKey)?;
209    let public_key = lines
210        .next()
211        .ok_or_else(|| SError::new(crate::ErrorKind::PublicKey, "missing public key"))?;
212    if lines.next().is_some() {
213        return Err(SError::new(
214            crate::ErrorKind::PublicKey,
215            "unexpected extra data",
216        ));
217    }
218    PublicKeyBox::new(Some(untrusted_comment), parse_raw_public_key(public_key)?)
219}
220#[cfg(test)]
221#[test]
222fn test_parse_public_key() {
223    use crate::KeyPairBox;
224    let password = b"password";
225    let k = KeyPairBox::generate(Some(password), None, None).unwrap();
226    let file = k.public_key_box.to_string();
227    let pk = parse_public_key(&file).unwrap();
228    assert_eq!(file, pk.to_string());
229}
230#[cfg(test)]
231#[test]
232fn test_parse_public_key_rejects_injected_comment() {
233    use crate::KeyPairBox;
234
235    let victim = KeyPairBox::generate(Some(b"victim"), None, None).unwrap();
236    let attacker = KeyPairBox::generate(Some(b"attacker"), None, None).unwrap();
237    let victim_raw = victim
238        .public_key_box
239        .to_string()
240        .lines()
241        .nth(1)
242        .unwrap()
243        .to_owned();
244    let attacker_raw = attacker
245        .public_key_box
246        .to_string()
247        .lines()
248        .nth(1)
249        .unwrap()
250        .to_owned();
251    let injected = format!("untrusted comment: comment\n{attacker_raw}\n{victim_raw}\n");
252
253    assert!(parse_public_key(&injected).is_err());
254}
255#[cfg(test)]
256#[test]
257fn test_parse_public_key_requires_comment_prefix() {
258    use crate::KeyPairBox;
259
260    let keypair = KeyPairBox::generate(Some(b"password"), None, None).unwrap();
261    let public_key = keypair
262        .public_key_box
263        .to_string()
264        .lines()
265        .nth(1)
266        .unwrap()
267        .to_owned();
268    let malformed = format!("bad comment\n{public_key}\n");
269
270    assert!(parse_public_key(&malformed).is_err());
271}
272/// A `PublicKey` is used to verify signatures.
273#[derive(Clone, Debug, PartialEq, Eq)]
274pub(crate) struct PublicKey {
275    pub sig_alg: [u8; 2],
276    pub key_id: [u8; 8],
277    pub key: RawPk,
278}
279impl PublicKey {
280    pub fn new(sig_alg: [u8; 2], key_id: [u8; 8], key: RawPk) -> Self {
281        Self {
282            sig_alg,
283            key_id,
284            key,
285        }
286    }
287}
288#[derive(Clone, Debug, PartialEq, Eq)]
289pub(crate) struct RawPk(pub ComponentBytes);
290impl RawPk {
291    pub fn new(key: ComponentBytes) -> Self {
292        Self(key)
293    }
294    pub fn verify(&self, msg: &[u8], sig: &ed25519::Signature) -> Result<bool> {
295        let pk = ed25519_dalek::VerifyingKey::from_bytes(&self.0)?;
296        Ok(pk.verify_strict(msg, sig).map(|_| true)?)
297    }
298}