mini_sign/
secret_key.rs

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