radicle_keystore/
memory.rs

1// This file is part of radicle-link
2// <https://github.com/radicle-dev/radicle-link>
3//
4// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License version 3 or
8// later as published by the Free Software Foundation.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18use std::{
19    fmt::{self, Debug, Display},
20    marker::PhantomData,
21};
22
23use crate::{crypto::Crypto, Keypair, Keystore, SecretKeyExt};
24
25struct Stored<PK, S, M> {
26    public_key: PK,
27    secret_key: S,
28    metadata: M,
29}
30
31/// [`Keystore`] implementation which stores the encrypted key in memory.
32///
33/// This is provided mainly for testing in environments where hitting the
34/// filesystem is undesirable, and otherwise equivalent to [`FileStorage`].
35///
36/// [`FileStorage`]: ../struct.FileStorage.html
37pub struct MemoryStorage<C: Crypto, PK, SK, M> {
38    key: Option<Stored<PK, C::SecretBox, M>>,
39    crypto: C,
40
41    _marker: PhantomData<SK>,
42}
43
44impl<C: Crypto, PK, SK, M> MemoryStorage<C, PK, SK, M> {
45    pub fn new(crypto: C) -> Self {
46        Self {
47            key: None,
48            crypto,
49
50            _marker: PhantomData,
51        }
52    }
53}
54
55#[derive(Debug)]
56pub enum Error<Crypto, Conversion> {
57    KeyExists,
58    NoSuchKey,
59    Crypto(Crypto),
60    Conversion(Conversion),
61}
62
63impl<Crypto, Conversion> std::error::Error for Error<Crypto, Conversion>
64where
65    Crypto: Display + Debug,
66    Conversion: Display + Debug,
67{
68}
69
70impl<Crypto, Conversion> Display for Error<Crypto, Conversion>
71where
72    Crypto: Display,
73    Conversion: Display,
74{
75    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76        match self {
77            Self::KeyExists => f.write_str("Key exists, refusing to overwrite"),
78            Self::NoSuchKey => f.write_str("No key found"),
79            Self::Conversion(e) => write!(f, "Error reconstructing sealed key: {}", e),
80            Self::Crypto(e) => write!(f, "Error unsealing key: {}", e),
81        }
82    }
83}
84
85impl<C, PK, SK, M> Keystore for MemoryStorage<C, PK, SK, M>
86where
87    C: Crypto,
88    C::Error: Display + Debug,
89    C::SecretBox: Clone,
90
91    SK: AsRef<[u8]> + SecretKeyExt<Metadata = M>,
92    <SK as SecretKeyExt>::Error: Display + Debug,
93
94    PK: Clone + From<SK>,
95    M: Clone,
96{
97    type PublicKey = PK;
98    type SecretKey = SK;
99    type Metadata = M;
100    type Error = Error<C::Error, <SK as SecretKeyExt>::Error>;
101
102    fn put_key(&mut self, key: Self::SecretKey) -> Result<(), Self::Error> {
103        if self.key.is_some() {
104            return Err(Error::KeyExists);
105        }
106
107        let metadata = key.metadata();
108        let sealed_key = self.crypto.seal(&key).map_err(Error::Crypto)?;
109        self.key = Some(Stored {
110            public_key: Self::PublicKey::from(key),
111            secret_key: sealed_key,
112            metadata,
113        });
114
115        Ok(())
116    }
117
118    fn get_key(&self) -> Result<Keypair<Self::PublicKey, Self::SecretKey>, Self::Error> {
119        match &self.key {
120            None => Err(Error::NoSuchKey),
121            Some(ref stored) => {
122                let sk = {
123                    let sbox = stored.secret_key.clone();
124                    let meta = stored.metadata.clone();
125
126                    self.crypto
127                        .unseal(sbox)
128                        .map_err(Error::Crypto)
129                        .and_then(|sec| {
130                            Self::SecretKey::from_bytes_and_meta(sec, &meta)
131                                .map_err(Error::Conversion)
132                        })
133                }?;
134
135                Ok(Keypair {
136                    public_key: stored.public_key.clone(),
137                    secret_key: sk,
138                })
139            },
140        }
141    }
142
143    fn show_key(&self) -> Result<(Self::PublicKey, Self::Metadata), Self::Error> {
144        self.key
145            .as_ref()
146            .ok_or(Error::NoSuchKey)
147            .map(|sealed| (sealed.public_key.clone(), sealed.metadata.clone()))
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::{
155        crypto::{self, Pwhash, SecretBoxError},
156        pinentry::Pinentry,
157        test::*,
158    };
159
160    fn with_mem_store<F, P>(pin: P, f: F)
161    where
162        F: FnOnce(MemoryStorage<Pwhash<P>, PublicKey, SecretKey, ()>),
163        P: Pinentry,
164        P::Error: std::error::Error + 'static,
165    {
166        f(MemoryStorage::new(Pwhash::new(
167            pin,
168            *crypto::KDF_PARAMS_TEST,
169        )))
170    }
171
172    #[test]
173    fn test_get_after_put() {
174        with_mem_store(default_passphrase(), get_after_put)
175    }
176
177    #[test]
178    fn test_put_twice() {
179        with_mem_store(default_passphrase(), |store| {
180            put_twice(store, Error::KeyExists)
181        })
182    }
183
184    #[test]
185    fn test_get_empty() {
186        with_mem_store(default_passphrase(), |store| {
187            get_empty(store, Error::NoSuchKey)
188        })
189    }
190
191    #[test]
192    fn test_passphrase_mismatch() {
193        with_mem_store(PinCycle::new(&["right".into(), "wrong".into()]), |store| {
194            passphrase_mismatch(store, Error::Crypto(SecretBoxError::InvalidKey))
195        })
196    }
197}