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