Skip to main content

minisign/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(
3    clippy::inherent_to_string,
4    clippy::wrong_self_convention,
5    clippy::derivable_impls,
6    clippy::field_reassign_with_default,
7    clippy::vec_init_then_push
8)]
9
10mod constants;
11mod crypto;
12mod errors;
13mod helpers;
14mod keynum;
15mod keypair;
16mod public_key;
17mod secret_key;
18mod signature;
19mod signature_bones;
20mod signature_box;
21
22#[cfg(test)]
23mod tests;
24
25use std::io::{self, Read, Seek, Write};
26
27use getrandom::fill as getrandom;
28
29pub use crate::constants::*;
30use crate::crypto::blake2b::Blake2b;
31use crate::crypto::ed25519;
32pub use crate::errors::*;
33use crate::helpers::*;
34pub use crate::keypair::*;
35pub use crate::public_key::*;
36pub use crate::secret_key::*;
37use crate::signature::*;
38pub use crate::signature_bones::*;
39pub use crate::signature_box::*;
40
41fn prehash<R>(data_reader: &mut R) -> Result<Vec<u8>>
42where
43    R: Read,
44{
45    let mut h = vec![0u8; PREHASH_BYTES];
46    let mut buf = vec![0u8; 65536];
47    let mut state = Blake2b::new(PREHASH_BYTES);
48    loop {
49        let len = data_reader.read(&mut buf)?;
50        if len == 0 {
51            break;
52        }
53        state.update(&buf[..len]);
54    }
55    state.finalize(&mut h);
56    Ok(h)
57}
58
59/// Compute a signature.
60///
61/// # Arguments
62///
63/// * `pk` - an optional public key. If provided, it must be the public key from
64///   the original key pair.
65/// * `sk` - the secret key
66/// * `data_reader` - the source of the data to be signed
67/// * `trusted_comment` - overrides the default trusted comment
68/// * `untrusted_comment` - overrides the default untrusted comment
69pub fn sign<R>(
70    pk: Option<&PublicKey>,
71    sk: &SecretKey,
72    mut data_reader: R,
73    trusted_comment: Option<&str>,
74    untrusted_comment: Option<&str>,
75) -> Result<SignatureBox>
76where
77    R: Read,
78{
79    if sk.is_encrypted() {
80        return Err(PError::new(
81            ErrorKind::EncryptedKey,
82            "Cannot sign with an encrypted secret key. The key must be decrypted first. Options include: SecretKeyBox::into_secret_key(password), SecretKey::from_box(box, password), SecretKey::from_file(path, password), or use KeyPair::generate_unencrypted_keypair() for passwordless keys.",
83        ));
84    }
85    let data = prehash(&mut data_reader)?;
86    let trusted_comment = match trusted_comment {
87        Some(trusted_comment) => trusted_comment.to_string(),
88        None => format!("timestamp:{}", unix_timestamp()),
89    };
90    let untrusted_comment = match untrusted_comment {
91        Some(untrusted_comment) => untrusted_comment.to_string(),
92        None => DEFAULT_COMMENT.to_string(),
93    };
94    let mut signature = Signature::default();
95    signature.sig_alg = SIGALG_PREHASHED;
96
97    signature.keynum.copy_from_slice(&sk.keynum_sk.keynum[..]);
98    let mut z = vec![0; 64];
99    getrandom(&mut z)?;
100    let signature_raw = ed25519::signature(&data, &sk.keynum_sk.sk, Some(&z));
101    signature.sig.copy_from_slice(&signature_raw[..]);
102
103    let mut sig_and_trusted_comment: Vec<u8> = vec![];
104    sig_and_trusted_comment.extend(signature.sig.iter());
105    sig_and_trusted_comment.extend(trusted_comment.as_bytes().iter());
106
107    getrandom(&mut z)?;
108    let global_sig = ed25519::signature(&sig_and_trusted_comment, &sk.keynum_sk.sk, Some(&z));
109    if let Some(pk) = pk {
110        if !ed25519::verify(&sig_and_trusted_comment, &pk.keynum_pk.pk[..], &global_sig) {
111            return Err(PError::new(
112                ErrorKind::Verify,
113                format!(
114                    "Could not verify signature with the provided public key ID: {:016X}",
115                    load_u64_le(&pk.keynum_pk.keynum[..])
116                ),
117            ));
118        }
119    }
120    let signature_box = SignatureBox {
121        untrusted_comment,
122        signature,
123        sig_and_trusted_comment: Some(sig_and_trusted_comment),
124        global_sig: Some(global_sig.to_vec()),
125        is_prehashed: true,
126    };
127    Ok(signature_box)
128}
129
130/// Verify a signature using a public key.
131///
132/// # Arguments
133///
134/// * `pk` - the public key
135/// * `signature_box` - the signature and its metadata
136/// * `data_reader` - the data source
137/// * `quiet` - use `false` to output status information to `stderr`
138/// * `output` - use `true` to output a copy of the data to `stdout`
139/// * `allow_legacy` - accept signatures from legacy versions of minisign
140pub fn verify<R>(
141    pk: &PublicKey,
142    signature_box: &SignatureBox,
143    mut data_reader: R,
144    quiet: bool,
145    output: bool,
146    allow_legacy: bool,
147) -> Result<()>
148where
149    R: Read + Seek,
150{
151    let data = if signature_box.is_prehashed() {
152        prehash(&mut data_reader)?
153    } else {
154        let mut data = vec![];
155        data_reader.read_to_end(&mut data)?;
156        data
157    };
158    let sig = &signature_box.signature;
159    if sig.keynum != pk.keynum_pk.keynum {
160        return Err(PError::new(
161            ErrorKind::Verify,
162            format!(
163                "Signature key id: {:016X} is different from public key: {:016X}",
164                load_u64_le(&sig.keynum[..]),
165                load_u64_le(&pk.keynum_pk.keynum[..])
166            ),
167        ));
168    }
169    if !allow_legacy && !signature_box.is_prehashed() {
170        return Err(PError::new(
171            ErrorKind::Verify,
172            "Legacy signatures are not accepted",
173        ));
174    }
175    if !ed25519::verify(&data, &pk.keynum_pk.pk, &sig.sig) {
176        return Err(PError::new(
177            ErrorKind::Verify,
178            "Signature verification failed",
179        ));
180    }
181    match (
182        &signature_box.sig_and_trusted_comment,
183        &signature_box.global_sig,
184    ) {
185        (Some(sig_and_trusted_comment), Some(global_sig)) => {
186            if !ed25519::verify(sig_and_trusted_comment, &pk.keynum_pk.pk, &global_sig[..]) {
187                return Err(PError::new(
188                    ErrorKind::Verify,
189                    "Comment signature verification failed",
190                ));
191            }
192        }
193        (None, None) => {}
194        _ => {
195            return Err(PError::new(
196                ErrorKind::Verify,
197                "Inconsistent signature presence for trusted comment presence",
198            ))
199        }
200    };
201    if !quiet {
202        eprintln!("Signature and comment signature verified");
203        if signature_box.global_sig.is_some() {
204            eprintln!("Trusted comment: {}", signature_box.trusted_comment()?);
205        }
206    }
207    if output {
208        data_reader.rewind()?;
209        let mut buf = vec![0; 65536];
210        loop {
211            let len = data_reader.read(&mut buf)?;
212            if len == 0 {
213                break;
214            }
215            io::stdout().write_all(&buf[..len])?;
216        }
217        io::stdout().flush()?;
218    }
219    Ok(())
220}