Skip to main content

mini_sign/
secret_key.rs

1use std::fmt::Display;
2
3use crate::{
4    public_key::{PublicKey, RawPk},
5    util::raw_scrypt_params,
6    PublicKeyBox, Result, SError, ALG_SIZE, CHK_ALG, CHK_SIZE, COMPONENT_SIZE, KDF_ALG,
7    KDF_LIMIT_SIZE, KDF_SALT_SIZE, KEYNUM_SK_SIZE, KEY_SIG_ALG, KID_SIZE, MEMLIMIT, N_LOG2_MAX,
8    OPSLIMIT,
9};
10use base64::Engine;
11use blake2::{Blake2b, Digest};
12use ed25519_dalek::{
13    ed25519::{self, ComponentBytes},
14    Signer,
15};
16use scrypt::password_hash::rand_core::{self, RngCore};
17use zeroize::{Zeroize, ZeroizeOnDrop};
18
19/// A `SecretKeyBox` represents a minisign secret key.
20///
21/// also can be output to a string and parse from a str.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct SecretKeyBox<'s> {
24    pub(crate) untrusted_comment: Option<&'s str>,
25    pub(crate) secret_key: SecretKey,
26}
27impl Display for SecretKeyBox<'_> {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        let mut s = String::new();
30        s.push_str("untrusted comment: ");
31        if let Some(c) = self.untrusted_comment {
32            s.push_str(c);
33        }
34        s.push('\n');
35        let encoder = base64::engine::general_purpose::STANDARD;
36        let mut sk_format = vec![];
37        sk_format.extend_from_slice(&self.secret_key.sig_alg);
38        sk_format.extend_from_slice(&self.secret_key.kdf_alg);
39        sk_format.extend_from_slice(&self.secret_key.cksum_alg);
40        sk_format.extend_from_slice(&self.secret_key.kdf_salt);
41        sk_format.extend_from_slice(&self.secret_key.kdf_opslimit.to_le_bytes());
42        sk_format.extend_from_slice(&self.secret_key.kdf_memlimit.to_le_bytes());
43        sk_format.extend_from_slice(&self.secret_key.keynum_sk);
44        let sk = encoder.encode(&sk_format);
45        s.push_str(&sk);
46        s.push('\n');
47
48        write!(f, "{}", s)
49    }
50}
51type Blake2b256 = Blake2b<blake2::digest::consts::U32>;
52impl<'s> SecretKeyBox<'s> {
53    fn new(untrusted_comment: Option<&'s str>, secret_key: SecretKey) -> Self {
54        Self {
55            untrusted_comment,
56            secret_key,
57        }
58    }
59    pub fn from_signing_key(
60        signing_key: ed25519_dalek::SigningKey,
61        kid: &[u8; KID_SIZE],
62        password: Option<&[u8]>,
63        untrusted_comment: Option<&'s str>,
64    ) -> Result<Self> {
65        let sk = signing_key.to_bytes();
66        let pk = signing_key.verifying_key().to_bytes();
67        let mut dest = [0u8; KDF_SALT_SIZE];
68        rand_core::OsRng.try_fill_bytes(&mut dest)?;
69
70        let mut hash = Blake2b256::new();
71        hash.update(KEY_SIG_ALG);
72        hash.update(kid);
73        hash.update(sk);
74        hash.update(pk);
75        let mut kdf_buf = kdf(password, &dest, OPSLIMIT, MEMLIMIT)?;
76        let keynum_sk = KeynumSK {
77            key_id: *kid,
78            sec_key: RawSk(sk),
79            pub_key: pk,
80            checksum: hash.finalize().to_vec().try_into().unwrap(),
81        };
82        kdf_buf = keynum_sk.to_bytes(kdf_buf);
83        let secret_key = SecretKey {
84            sig_alg: KEY_SIG_ALG,
85            kdf_alg: KDF_ALG,
86            cksum_alg: CHK_ALG,
87            kdf_salt: dest,
88            kdf_opslimit: OPSLIMIT,
89            kdf_memlimit: MEMLIMIT,
90            keynum_sk: kdf_buf,
91        };
92
93        Ok(Self::new(untrusted_comment, secret_key))
94    }
95    pub(crate) fn sign(
96        &self,
97        message: &[u8],
98        password: Option<&[u8]>,
99    ) -> Result<ed25519::Signature> {
100        self.secret_key.sign(message, password)
101    }
102    pub(crate) fn xor_keynum_sk(&self, password: Option<&[u8]>) -> Result<KeynumSK> {
103        self.secret_key.xor_keynum_sk(password)
104    }
105    /// Get the public key from the secret key, without untrusted comment.
106    /// only one line.
107    pub fn from_raw_str(s: &'s str) -> Result<Self> {
108        let secret_key = s.trim();
109        let decoder = base64::engine::general_purpose::STANDARD;
110        let sk_format = decoder
111            .decode(secret_key.as_bytes())
112            .map_err(|e| SError::new(crate::ErrorKind::SecretKey, e))?;
113        if sk_format.len()
114            != ALG_SIZE
115                + ALG_SIZE
116                + ALG_SIZE
117                + KDF_SALT_SIZE
118                + KDF_LIMIT_SIZE
119                + KDF_LIMIT_SIZE
120                + KEYNUM_SK_SIZE
121        {
122            return Err(SError::new(
123                crate::ErrorKind::SecretKey,
124                "invalid secret key length",
125            ));
126        }
127        let sig_alg = &sk_format[..ALG_SIZE];
128        let kdf_alg = &sk_format[ALG_SIZE..ALG_SIZE + ALG_SIZE];
129        let cksum_alg = &sk_format[ALG_SIZE + ALG_SIZE..ALG_SIZE + ALG_SIZE + ALG_SIZE];
130        let kdf_salt = &sk_format
131            [ALG_SIZE + ALG_SIZE + ALG_SIZE..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE];
132        let kdf_opslimit = u64::from_le_bytes(
133            sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE
134                ..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE]
135                .try_into()
136                .unwrap(),
137        );
138        let kdf_memlimit = u64::from_le_bytes(
139            sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE
140                ..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE + KDF_LIMIT_SIZE]
141                .try_into()
142                .unwrap(),
143        );
144
145        let secret_key = SecretKey {
146            sig_alg: sig_alg.try_into().unwrap(),
147            kdf_alg: kdf_alg.try_into().unwrap(),
148            cksum_alg: cksum_alg.try_into().unwrap(),
149            kdf_salt: kdf_salt.try_into().unwrap(),
150            kdf_opslimit,
151            kdf_memlimit,
152            keynum_sk: sk_format[ALG_SIZE
153                + ALG_SIZE
154                + ALG_SIZE
155                + KDF_SALT_SIZE
156                + KDF_LIMIT_SIZE
157                + KDF_LIMIT_SIZE..]
158                .try_into()
159                .unwrap(),
160        };
161        Ok(SecretKeyBox::new(None, secret_key))
162    }
163    /// Parse a `SecretKeyBox` from str.
164    ///
165    /// as it store in a file.
166    #[allow(clippy::should_implement_trait)]
167    pub fn from_str(s: &'s str) -> Result<Self> {
168        parse_secret_key(s)
169    }
170    /// Get the untrusted comment.
171    pub fn untrusted_comment(&self) -> Option<&'s str> {
172        self.untrusted_comment
173    }
174    /// Get public key from the secret key.
175    pub fn public_key(&self, password: Option<&[u8]>) -> Result<PublicKeyBox<'s>> {
176        pub_key_from_sec_key(self, password)
177    }
178}
179fn pub_key_from_sec_key<'s>(
180    sec_key: &SecretKeyBox<'s>,
181    password: Option<&[u8]>,
182) -> Result<PublicKeyBox<'s>> {
183    let keynum_sk = sec_key.xor_keynum_sk(password)?;
184    let pk_box = PublicKeyBox::new(
185        None,
186        PublicKey::new(
187            sec_key.secret_key.sig_alg,
188            keynum_sk.key_id,
189            RawPk(keynum_sk.pub_key),
190        ),
191    );
192    Ok(pk_box)
193}
194
195fn parse_raw_secret_key(secret_key: &str) -> Result<SecretKey> {
196    let decoder = base64::engine::general_purpose::STANDARD;
197    let sk_format = decoder
198        .decode(secret_key.as_bytes())
199        .map_err(|e| SError::new(crate::ErrorKind::SecretKey, e))?;
200    if sk_format.len()
201        != ALG_SIZE
202            + ALG_SIZE
203            + ALG_SIZE
204            + KDF_SALT_SIZE
205            + KDF_LIMIT_SIZE
206            + KDF_LIMIT_SIZE
207            + KEYNUM_SK_SIZE
208    {
209        return Err(SError::new(
210            crate::ErrorKind::SecretKey,
211            "invalid secret key length",
212        ));
213    }
214    let sig_alg = &sk_format[..ALG_SIZE];
215    let kdf_alg = &sk_format[ALG_SIZE..ALG_SIZE + ALG_SIZE];
216    let cksum_alg = &sk_format[ALG_SIZE + ALG_SIZE..ALG_SIZE + ALG_SIZE + ALG_SIZE];
217    let kdf_salt =
218        &sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE];
219    let kdf_opslimit = u64::from_le_bytes(
220        sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE
221            ..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE]
222            .try_into()
223            .unwrap(),
224    );
225    let kdf_memlimit = u64::from_le_bytes(
226        sk_format[ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE
227            ..ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE + KDF_LIMIT_SIZE]
228            .try_into()
229            .unwrap(),
230    );
231
232    let secret_key = SecretKey {
233        sig_alg: sig_alg.try_into().unwrap(),
234        kdf_alg: kdf_alg.try_into().unwrap(),
235        cksum_alg: cksum_alg.try_into().unwrap(),
236        kdf_salt: kdf_salt.try_into().unwrap(),
237        kdf_opslimit,
238        kdf_memlimit,
239        keynum_sk: sk_format
240            [ALG_SIZE + ALG_SIZE + ALG_SIZE + KDF_SALT_SIZE + KDF_LIMIT_SIZE + KDF_LIMIT_SIZE..]
241            .try_into()
242            .unwrap(),
243    };
244    Ok(secret_key)
245}
246fn parse_secret_key(s: &str) -> Result<SecretKeyBox<'_>> {
247    let mut lines = s.lines();
248    if let Some(c) = lines.next() {
249        let untrusted_comment = c.strip_prefix("untrusted comment: ");
250        let secret_key = lines
251            .next()
252            .ok_or_else(|| SError::new(crate::ErrorKind::SecretKey, "missing secret key"))?;
253        Ok(SecretKeyBox::new(
254            untrusted_comment,
255            parse_raw_secret_key(secret_key)?,
256        ))
257    } else {
258        Err(SError::new(
259            crate::ErrorKind::SecretKey,
260            "missing untrusted comment",
261        ))
262    }
263}
264
265#[cfg(test)]
266#[test]
267fn test_parse_secret_key() {
268    use crate::KeyPairBox;
269    let password = b"password";
270    let k = KeyPairBox::generate(Some(password), None, None).unwrap();
271    let file = k.secret_key_box.to_string();
272    let sk = parse_secret_key(&file).unwrap();
273    assert_eq!(file, sk.to_string());
274}
275/// A `SecretKey` is used to sign messages.
276#[derive(Clone, Debug, ZeroizeOnDrop, PartialEq, Eq)]
277pub(crate) struct SecretKey {
278    pub sig_alg: [u8; ALG_SIZE],
279    kdf_alg: [u8; ALG_SIZE],
280    cksum_alg: [u8; ALG_SIZE],
281    kdf_salt: [u8; KDF_SALT_SIZE],
282    kdf_opslimit: u64,
283    kdf_memlimit: u64,
284    keynum_sk: [u8; KEYNUM_SK_SIZE],
285}
286#[derive(Clone, Debug, ZeroizeOnDrop)]
287pub struct KeynumSK {
288    pub(crate) key_id: [u8; KID_SIZE],
289    sec_key: RawSk,
290    pub pub_key: ComponentBytes,
291    checksum: [u8; CHK_SIZE],
292}
293impl KeynumSK {
294    fn to_bytes(&self, mut kdf_buf: [u8; KEYNUM_SK_SIZE]) -> [u8; KEYNUM_SK_SIZE] {
295        for (i, item) in kdf_buf.iter_mut().enumerate().take(KID_SIZE) {
296            *item ^= self.key_id[i];
297        }
298        for i in 0..COMPONENT_SIZE {
299            kdf_buf[KID_SIZE + i] ^= self.sec_key.0[i];
300        }
301        for i in 0..COMPONENT_SIZE {
302            kdf_buf[KID_SIZE + COMPONENT_SIZE + i] ^= self.pub_key[i];
303        }
304        for i in 0..CHK_SIZE {
305            kdf_buf[KID_SIZE + 2 * COMPONENT_SIZE + i] ^= self.checksum[i];
306        }
307        kdf_buf
308    }
309    fn from_bytes(keynum_sk: &[u8; KEYNUM_SK_SIZE], mut kdf_buf: [u8; KEYNUM_SK_SIZE]) -> Self {
310        // let mut kdf_buf = [0u8; KEYNUM_SK_SIZE];
311        for i in 0..KEYNUM_SK_SIZE {
312            kdf_buf[i] ^= keynum_sk[i];
313        }
314        Self {
315            key_id: kdf_buf[0..KID_SIZE].try_into().unwrap(),
316            sec_key: RawSk(
317                kdf_buf[KID_SIZE..KID_SIZE + COMPONENT_SIZE]
318                    .try_into()
319                    .unwrap(),
320            ),
321            pub_key: kdf_buf[KID_SIZE + COMPONENT_SIZE..KID_SIZE + 2 * COMPONENT_SIZE]
322                .try_into()
323                .unwrap(),
324            checksum: kdf_buf[KID_SIZE + 2 * COMPONENT_SIZE..KEYNUM_SK_SIZE]
325                .try_into()
326                .unwrap(),
327        }
328    }
329}
330#[derive(Debug, Clone, ZeroizeOnDrop, Zeroize)]
331struct RawSk(ComponentBytes);
332impl Signer<ed25519::Signature> for RawSk {
333    fn try_sign(&self, msg: &[u8]) -> std::result::Result<ed25519::Signature, ed25519::Error> {
334        let sk = ed25519_dalek::SigningKey::from_bytes(&self.0);
335        Ok(sk.sign(msg))
336    }
337}
338fn kdf(
339    password: Option<&[u8]>,
340    salt: &[u8; KDF_SALT_SIZE],
341    opslimit: u64,
342    memlimit: u64,
343) -> Result<[u8; KEYNUM_SK_SIZE]> {
344    let params = raw_scrypt_params(memlimit as usize, opslimit, N_LOG2_MAX)?;
345    let mut stream = [0u8; KEYNUM_SK_SIZE];
346    scrypt::scrypt(password.unwrap_or(&[]), salt, &params, &mut stream)?;
347    Ok(stream)
348}
349impl SecretKey {
350    pub fn sign(&self, message: &[u8], password: Option<&[u8]>) -> Result<ed25519::Signature> {
351        let keynum_sk = self.xor_keynum_sk(password);
352        Ok(keynum_sk?.sec_key.sign(message))
353    }
354    pub(crate) fn xor_keynum_sk(&self, password: Option<&[u8]>) -> Result<KeynumSK> {
355        let stream = kdf(
356            password,
357            &self.kdf_salt,
358            self.kdf_opslimit,
359            self.kdf_memlimit,
360        )?;
361
362        let keynum_sk = KeynumSK::from_bytes(&self.keynum_sk, stream);
363
364        let mut hash = Blake2b256::new();
365        hash.update(self.sig_alg);
366        hash.update(&keynum_sk.key_id);
367        hash.update(&keynum_sk.sec_key.0);
368        hash.update(&keynum_sk.pub_key);
369        if hash.finalize().to_vec() != keynum_sk.checksum {
370            return Err(SError::new(
371                crate::ErrorKind::SecretKey,
372                "checksum mismatch, invalid password",
373            ));
374        }
375        Ok(keynum_sk)
376    }
377}
378#[cfg(test)]
379#[test]
380fn test_sign() {
381    use crate::{pub_key_from_sec_key, KeyPairBox};
382    let password = b"password";
383    let k = KeyPairBox::generate(Some(password), None, None).unwrap();
384    let s = k.secret_key_box.to_string();
385    let sk = parse_secret_key(&s).unwrap();
386    let msg = b"hello world";
387    let sig = sk.sign(msg, Some(password)).unwrap();
388    let pk = pub_key_from_sec_key(&sk, Some(password)).unwrap();
389    let v = pk.public_key.key.verify(msg, &sig);
390    assert_eq!(v.unwrap(), true);
391}