seshat/index/
encrypted_dir.rs

1// Copyright 2019 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use rand::{thread_rng, Rng};
16use std::{
17    fs::File,
18    io::{BufWriter, Cursor, Error as IoError, ErrorKind, Read, Write},
19    path::Path,
20};
21
22use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
23
24use aes::{
25    cipher::{KeyIvInit, StreamCipher},
26    Aes256,
27};
28use hkdf::Hkdf;
29use hmac::{Hmac, Mac, NewMac};
30use pbkdf2::pbkdf2;
31use sha2::{Sha256, Sha512};
32
33use tantivy::directory::{
34    error::{
35        DeleteError, IOError as TvIoError, LockError, OpenDirectoryError, OpenReadError,
36        OpenWriteError,
37    },
38    AntiCallToken, Directory, DirectoryLock, Lock, ReadOnlySource, TerminatingWrite, WatchCallback,
39    WatchHandle, WritePtr,
40};
41
42use zeroize::Zeroizing;
43
44type Aes256Ctr = ctr::Ctr128BE<Aes256>;
45
46use crate::index::encrypted_stream::{AesReader, AesWriter};
47
48/// KeyBuffer type that makes sure that the buffer is zeroed out before being
49/// dropped.
50type KeyBuffer = Zeroizing<Vec<u8>>;
51
52/// Key derivation result type for our initial key derivation. Consists of a
53/// tuple containing a encryption key, a MAC key, and a random salt.
54type InitialKeyDerivationResult = (KeyBuffer, KeyBuffer, Vec<u8>);
55
56/// Key derivation result for our subsequent key derivations. The salt will be
57/// read from our key file and we will re-derive our encryption and MAC keys.
58type KeyDerivationResult = (KeyBuffer, KeyBuffer);
59
60// The constants here are chosen to be similar to the constants for the Matrix
61// key export format[1].
62// [1] https://matrix.org/docs/spec/client_server/r0.5.0#key-exports
63const KEYFILE: &str = "seshat-index.key";
64// 16 byte random salt.
65const SALT_SIZE: usize = 16;
66// 16 byte random IV for the AES-CTR mode.
67const IV_SIZE: usize = 16;
68// 32 byte or 256 bit encryption keys.
69const KEY_SIZE: usize = 32;
70// 32 byte message authentication code since HMAC-SHA256 is used.
71const MAC_LENGTH: usize = 32;
72// 1 byte for the store version.
73const VERSION: u8 = 1;
74
75#[cfg(test)]
76// Tests don't need to protect against brute force attacks.
77pub(crate) const PBKDF_COUNT: u32 = 10;
78
79#[cfg(not(test))]
80// This is quite a bit lower than the spec since the encrypted key and index
81// will not be public. An attacker would need to get hold of the encrypted index,
82// its key and only then move on to bruteforcing the key. Even if the attacker
83// decrypts the index he wouldn't get to the events itself, only to the index
84// of them.
85pub(crate) const PBKDF_COUNT: u32 = 10_000;
86
87#[derive(Clone, Debug)]
88/// A Directory implementation that wraps a MmapDirectory and adds [AES][aes]
89/// based encryption to the file read/write operations.
90///
91/// When a new EncryptedMmapDirectory is created a random 256 bit AES key is
92/// generated, the store key. This key is encrypted using a key that will be
93/// derived with the user provided passphrase using [PBKDF2][pbkdf]. We generate
94/// a random 128 bit salt and derive a 512 bit key using PBKDF2:
95///
96/// ```text
97///     derived_key = PBKDF2(SHA512, passphrase, salt, count, 512)
98/// ```
99///
100/// After key derivation, the key is split into a 256 bit AES encryption key and
101/// a 256 bit MAC key.
102///
103/// The store key will be encrypted using AES-CTR with the encryption key that
104/// was derived before, a random IV will be generated before encrypting the
105/// store_key:
106///
107/// ```text
108///     ciphertext = AES256-CTR(iv, store_key)
109/// ```
110///
111/// A MAC of the encrypted ciphertext will be created using
112/// the derived MAC key and [HMAC-SHA256][hmac]:
113///
114/// ```text
115///     mac = HMAC-SHA256(mac_key, version || iv || salt || ciphertext)
116/// ```
117///
118/// The store key will be written to a file concatenated with a store version,
119/// IV, salt, PBKDF count, and MAC. The PBKDF count will be stored using the big
120/// endian byte order:
121///
122/// ```text
123///     key_file = (version || iv || salt || pbkdf_count || mac || key_ciphertext)
124/// ```
125///
126/// Our store key will be used to encrypt the many files that Tantivy generates.
127/// For this, the store key will be expanded into an 256 bit encryption key and
128/// a 256 bit MAC key using [HKDF][hkdf].
129///
130/// ```text
131///     encryption_key, mac_key = HKDF(SHA512, store_key, "", 512)
132/// ```
133///
134/// Those two keys are used to encrypt and authenticate the Tantivy files. The
135/// encryption scheme is similar to the one used for the store key, AES-CTR mode
136/// for encryption and HMAC-SHA256 for authentication. For every encrypted file
137/// a new random 128 bit IV will be generated.
138///
139/// The MAC will be calculated only on the ciphertext:
140///
141/// ```text
142///     mac = HMAC-SHA256(mac_key, ciphertext)
143/// ```
144///
145/// The file format differs a bit, the MAC will be at the end of the file and
146/// the ciphertext is between the IV and salt:
147///
148/// ```text
149///     file_data = (iv || ciphertext || mac)
150/// ```
151///
152/// [aes]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
153/// [pbkdf]: https://en.wikipedia.org/wiki/PBKDF2
154/// [hkdf]: https://en.wikipedia.org/wiki/HKDF
155/// [hmac]: https://en.wikipedia.org/wiki/HMAC
156pub struct EncryptedMmapDirectory {
157    mmap_dir: tantivy::directory::MmapDirectory,
158    encryption_key: KeyBuffer,
159    mac_key: KeyBuffer,
160}
161
162impl EncryptedMmapDirectory {
163    fn new(store_key: KeyBuffer, path: &Path) -> Result<Self, OpenDirectoryError> {
164        // Expand the store key into a encryption and MAC key.
165        let (encryption_key, mac_key) = EncryptedMmapDirectory::expand_store_key(&store_key)?;
166
167        // Open our underlying bare Tantivy mmap based directory.
168        let mmap_dir = tantivy::directory::MmapDirectory::open(path)?;
169
170        Ok(EncryptedMmapDirectory {
171            mmap_dir,
172            encryption_key,
173            mac_key,
174        })
175    }
176    /// Open a encrypted mmap directory. If the directory is empty a new
177    ///   directory key will be generated and encrypted with the given passphrase.
178    ///
179    /// If a new store is created, this method will randomly generated a new
180    ///   store key and encrypted using the given passphrase.
181    ///
182    /// # Arguments
183    ///
184    /// * `path` - The path where the directory should reside in.
185    /// * `passphrase` - The passphrase that was used to encrypt our directory
186    ///   or the one that will be used to encrypt our directory.
187    /// * `key_derivation_count` - The number of iterations that our key
188    ///   derivation function should use. Can't be lower than 1, should be chosen
189    ///   as high as possible, depending on how much time is acceptable for the
190    ///   caller to wait. Is only used when a new store is created. The count will
191    ///   be stored with the store key.
192    ///
193    /// Returns an error if the path does not exist, if it is not a directory or
194    ///   if there was an error when trying to decrypt the directory key e.g. the
195    ///   given passphrase was incorrect.
196    pub fn open_or_create<P: AsRef<Path>>(
197        path: P,
198        passphrase: &str,
199        key_derivation_count: u32,
200    ) -> Result<Self, OpenDirectoryError> {
201        if passphrase.is_empty() {
202            return Err(IoError::new(ErrorKind::Other, "empty passphrase").into());
203        }
204
205        if key_derivation_count == 0 {
206            return Err(IoError::new(ErrorKind::Other, "invalid key derivation count").into());
207        }
208
209        let key_path = path.as_ref().join(KEYFILE);
210        let key_file = File::open(&key_path);
211
212        // Either load a store key or create a new store key if the key file
213        // doesn't exist.
214        let store_key = match key_file {
215            Ok(k) => {
216                let (_, key) = EncryptedMmapDirectory::load_store_key(k, passphrase)?;
217                key
218            }
219            Err(e) => {
220                if e.kind() != ErrorKind::NotFound {
221                    return Err(e.into());
222                }
223                EncryptedMmapDirectory::create_new_store(
224                    &key_path,
225                    passphrase,
226                    key_derivation_count,
227                )?
228            }
229        };
230        EncryptedMmapDirectory::new(store_key, path.as_ref())
231    }
232
233    /// Open a encrypted mmap directory.
234    ///
235    /// # Arguments
236    ///
237    /// * `path` - The path where the directory should reside in.
238    /// * `passphrase` - The passphrase that was used to encrypt our directory.
239    ///
240    /// Returns an error if the path does not exist, if it is not a directory or
241    ///   if there was an error when trying to decrypt the directory key e.g. the
242    ///   given passphrase was incorrect.
243    // This isn't currently used anywhere, but it will make sense if the
244    // EncryptedMmapDirectory gets upstreamed.
245    #[allow(dead_code)]
246    pub fn open<P: AsRef<Path>>(path: P, passphrase: &str) -> Result<Self, OpenDirectoryError> {
247        if passphrase.is_empty() {
248            return Err(IoError::new(ErrorKind::Other, "empty passphrase").into());
249        }
250
251        let key_path = path.as_ref().join(KEYFILE);
252        let key_file = File::open(key_path)?;
253
254        // Expand the store key into a encryption and MAC key.
255        let (_, store_key) = EncryptedMmapDirectory::load_store_key(key_file, passphrase)?;
256        EncryptedMmapDirectory::new(store_key, path.as_ref())
257    }
258
259    /// Change the passphrase that is used to encrypt the store key.
260    /// This will decrypt and re-encrypt the store key using the new passphrase.
261    ///
262    /// # Arguments
263    ///
264    /// * `path` - The path where the directory resides in.
265    /// * `old_passphrase` - The currently used passphrase.
266    /// * `new_passphrase` - The passphrase that should be used from now on.
267    /// * `new_key_derivation_count` - The key derivation count that should be
268    ///   used for the re-encrypted store key.
269    pub fn change_passphrase<P: AsRef<Path>>(
270        path: P,
271        old_passphrase: &str,
272        new_passphrase: &str,
273        new_key_derivation_count: u32,
274    ) -> Result<(), OpenDirectoryError> {
275        if old_passphrase.is_empty() || new_passphrase.is_empty() {
276            return Err(IoError::new(ErrorKind::Other, "empty passphrase").into());
277        }
278        if new_key_derivation_count == 0 {
279            return Err(IoError::new(ErrorKind::Other, "invalid key derivation count").into());
280        }
281
282        let key_path = path.as_ref().join(KEYFILE);
283        let key_file = File::open(&key_path)?;
284
285        // Load our store key using the old passphrase.
286        let (_, store_key) = EncryptedMmapDirectory::load_store_key(key_file, old_passphrase)?;
287        // Derive new encryption keys using the new passphrase.
288        let (key, hmac_key, salt) =
289            EncryptedMmapDirectory::derive_key(new_passphrase, new_key_derivation_count)?;
290        // Re-encrypt our store key using the newly derived keys.
291        EncryptedMmapDirectory::encrypt_store_key(
292            &key,
293            &salt,
294            new_key_derivation_count,
295            &hmac_key,
296            &store_key,
297            &key_path,
298        )?;
299
300        Ok(())
301    }
302
303    /// Expand the given store key into an encryption key and HMAC key.
304    fn expand_store_key(store_key: &[u8]) -> std::io::Result<KeyDerivationResult> {
305        let mut hkdf_result = Zeroizing::new([0u8; KEY_SIZE * 2]);
306
307        let hkdf = Hkdf::<Sha512>::new(None, store_key);
308        hkdf.expand(&[], &mut *hkdf_result).map_err(|e| {
309            IoError::new(
310                ErrorKind::Other,
311                format!("unable to expand store key: {:?}", e),
312            )
313        })?;
314        let (key, hmac_key) = hkdf_result.split_at(KEY_SIZE);
315        Ok((
316            Zeroizing::new(Vec::from(key)),
317            Zeroizing::new(Vec::from(hmac_key)),
318        ))
319    }
320
321    /// Load a store key from the given file and decrypt it using the given
322    /// passphrase.
323    fn load_store_key(
324        mut key_file: File,
325        passphrase: &str,
326    ) -> Result<(u32, KeyBuffer), OpenDirectoryError> {
327        let mut iv = [0u8; IV_SIZE];
328        let mut salt = [0u8; SALT_SIZE];
329        let mut expected_mac = [0u8; MAC_LENGTH];
330        let mut version = [0u8; 1];
331        let mut encrypted_key = vec![];
332
333        // Read our iv, salt, mac, and encrypted key from our key file.
334        key_file.read_exact(&mut version)?;
335        key_file.read_exact(&mut iv)?;
336        key_file.read_exact(&mut salt)?;
337        let pbkdf_count = key_file.read_u32::<BigEndian>()?;
338        key_file.read_exact(&mut expected_mac)?;
339
340        // Our key will be AES encrypted in CTR mode meaning the ciphertext
341        // will have the same size as the plaintext. Read at most KEY_SIZE
342        // bytes here so we don't end up filling up memory unnecessarily if
343        // someone modifies the file.
344        key_file
345            .take(KEY_SIZE as u64)
346            .read_to_end(&mut encrypted_key)?;
347
348        if version[0] != VERSION {
349            return Err(IoError::new(ErrorKind::Other, "invalid index store version").into());
350        }
351
352        // Re-derive our key using the passphrase and salt.
353        let (key, hmac_key) = EncryptedMmapDirectory::rederive_key(passphrase, &salt, pbkdf_count);
354
355        // First check our MAC of the encrypted key.
356        let mac = EncryptedMmapDirectory::calculate_hmac(
357            version[0],
358            &iv,
359            &salt,
360            &encrypted_key,
361            &hmac_key,
362        )?;
363
364        if mac.verify(&expected_mac).is_err() {
365            return Err(IoError::new(ErrorKind::Other, "invalid MAC of the store key").into());
366        }
367
368        let mut decryptor = Aes256Ctr::new_from_slices(&key, &iv).map_err(|e| {
369            IoError::new(
370                ErrorKind::Other,
371                format!("error initializing cipher {:?}", e),
372            )
373        })?;
374
375        let mut out = Zeroizing::new(encrypted_key);
376        decryptor.try_apply_keystream(&mut out).map_err(|_| {
377            IoError::new(
378                ErrorKind::Other,
379                "Decryption error, reached end of the keystream.",
380            )
381        })?;
382
383        Ok((pbkdf_count, out))
384    }
385
386    /// Calculate a HMAC for the given inputs.
387    fn calculate_hmac(
388        version: u8,
389        iv: &[u8],
390        salt: &[u8],
391        encrypted_data: &[u8],
392        hmac_key: &[u8],
393    ) -> std::io::Result<Hmac<Sha256>> {
394        let mut hmac = Hmac::<Sha256>::new_from_slice(hmac_key)
395            .map_err(|e| IoError::new(ErrorKind::Other, format!("error creating hmac: {:?}", e)))?;
396        hmac.update(&[version]);
397        hmac.update(iv);
398        hmac.update(salt);
399        hmac.update(encrypted_data);
400        Ok(hmac)
401    }
402
403    /// Create a new store key, encrypt it with the given passphrase and store
404    /// it in the given path.
405    fn create_new_store(
406        key_path: &Path,
407        passphrase: &str,
408        pbkdf_count: u32,
409    ) -> Result<KeyBuffer, OpenDirectoryError> {
410        // Derive a AES key from our passphrase using a randomly generated salt
411        // to prevent bruteforce attempts using rainbow tables.
412        let (key, hmac_key, salt) = EncryptedMmapDirectory::derive_key(passphrase, pbkdf_count)?;
413        // Generate a new random store key. This key will encrypt our Tantivy
414        // indexing files. The key itself is stored encrypted using the derived
415        // key.
416        let store_key = EncryptedMmapDirectory::generate_key()?;
417
418        // Encrypt and save the encrypted store key to a file.
419        EncryptedMmapDirectory::encrypt_store_key(
420            &key,
421            &salt,
422            pbkdf_count,
423            &hmac_key,
424            &store_key,
425            key_path,
426        )?;
427
428        Ok(store_key)
429    }
430
431    /// Encrypt the given store key and save it in the given path.
432    fn encrypt_store_key(
433        key: &[u8],
434        salt: &[u8],
435        pbkdf_count: u32,
436        hmac_key: &[u8],
437        store_key: &[u8],
438        key_path: &Path,
439    ) -> Result<(), OpenDirectoryError> {
440        // Generate a random initialization vector for our AES encryptor.
441        let iv = EncryptedMmapDirectory::generate_iv()?;
442        let mut encryptor = Aes256Ctr::new_from_slices(key, &iv).map_err(|e| {
443            IoError::new(
444                ErrorKind::Other,
445                format!("error initializing cipher: {:?}", e),
446            )
447        })?;
448
449        let mut encrypted_key = [0u8; KEY_SIZE];
450        encrypted_key.copy_from_slice(store_key);
451
452        let mut key_file = File::create(key_path)?;
453
454        // Write down our public salt and iv first, those will be needed to
455        // decrypt the key again.
456        key_file.write_all(&[VERSION])?;
457        key_file.write_all(&iv)?;
458        key_file.write_all(salt)?;
459        key_file.write_u32::<BigEndian>(pbkdf_count)?;
460
461        // Encrypt our key.
462        encryptor
463            .try_apply_keystream(&mut encrypted_key)
464            .map_err(|e| {
465                IoError::new(
466                    ErrorKind::Other,
467                    format!("unable to encrypt store key: {:?}", e),
468                )
469            })?;
470
471        // Calculate a MAC for our encrypted key and store it in the file before
472        // the key.
473        let mac =
474            EncryptedMmapDirectory::calculate_hmac(VERSION, &iv, salt, &encrypted_key, hmac_key)?;
475        let mac = mac.finalize();
476        let mac = mac.into_bytes();
477        key_file.write_all(mac.as_slice())?;
478
479        // Write down the encrypted key.
480        key_file.write_all(&encrypted_key)?;
481
482        Ok(())
483    }
484
485    /// Generate a random IV.
486    fn generate_iv() -> Result<[u8; IV_SIZE], OpenDirectoryError> {
487        let mut iv = [0u8; IV_SIZE];
488        let mut rng = thread_rng();
489        rng.try_fill(&mut iv[..])
490            .map_err(|e| IoError::new(ErrorKind::Other, format!("error generating iv: {:?}", e)))?;
491        Ok(iv)
492    }
493
494    /// Generate a random key.
495    fn generate_key() -> Result<KeyBuffer, OpenDirectoryError> {
496        let mut key = Zeroizing::new(vec![0u8; KEY_SIZE]);
497        let mut rng = thread_rng();
498        rng.try_fill(&mut key[..]).map_err(|e| {
499            IoError::new(ErrorKind::Other, format!("error generating key: {:?}", e))
500        })?;
501        Ok(key)
502    }
503
504    /// Derive two keys from the given passphrase and the given salt using PBKDF2.
505    fn rederive_key(passphrase: &str, salt: &[u8], pbkdf_count: u32) -> KeyDerivationResult {
506        let mut pbkdf_result = Zeroizing::new([0u8; KEY_SIZE * 2]);
507
508        pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt, pbkdf_count, &mut *pbkdf_result);
509        let (key, hmac_key) = pbkdf_result.split_at(KEY_SIZE);
510        (
511            Zeroizing::new(Vec::from(key)),
512            Zeroizing::new(Vec::from(hmac_key)),
513        )
514    }
515
516    /// Generate a random salt and derive two keys from the salt and the given
517    /// passphrase.
518    fn derive_key(
519        passphrase: &str,
520        pbkdf_count: u32,
521    ) -> Result<InitialKeyDerivationResult, OpenDirectoryError> {
522        let mut rng = thread_rng();
523        let mut salt = vec![0u8; SALT_SIZE];
524        rng.try_fill(&mut salt[..]).map_err(|e| {
525            IoError::new(ErrorKind::Other, format!("error generating salt: {:?}", e))
526        })?;
527
528        let (key, hmac_key) = EncryptedMmapDirectory::rederive_key(passphrase, &salt, pbkdf_count);
529        Ok((key, hmac_key, salt))
530    }
531}
532
533// The Directory trait[dr] implementation for our EncryptedMmapDirectory.
534// [dr] https://docs.rs/tantivy/0.10.2/tantivy/directory/trait.Directory.html
535impl Directory for EncryptedMmapDirectory {
536    fn open_read(&self, path: &Path) -> Result<ReadOnlySource, OpenReadError> {
537        let source = self.mmap_dir.open_read(path)?;
538
539        let mut reader = AesReader::<Aes256Ctr, _>::new::<Hmac<Sha256>>(
540            Cursor::new(source.as_slice()),
541            &self.encryption_key,
542            &self.mac_key,
543            IV_SIZE,
544            MAC_LENGTH,
545        )
546        .map_err(TvIoError::from)?;
547
548        let mut decrypted = Vec::new();
549        reader
550            .read_to_end(&mut decrypted)
551            .map_err(TvIoError::from)?;
552
553        Ok(ReadOnlySource::from(decrypted))
554    }
555
556    fn delete(&self, path: &Path) -> Result<(), DeleteError> {
557        self.mmap_dir.delete(path)
558    }
559
560    fn exists(&self, path: &Path) -> bool {
561        self.mmap_dir.exists(path)
562    }
563
564    fn open_write(&mut self, path: &Path) -> Result<WritePtr, OpenWriteError> {
565        let file = match self.mmap_dir.open_write(path)?.into_inner() {
566            Ok(f) => f,
567            Err(e) => {
568                let error = IoError::from(e);
569                return Err(TvIoError::from(error).into());
570            }
571        };
572
573        let writer = AesWriter::<Aes256Ctr, Hmac<Sha256>, _>::new(
574            file,
575            &self.encryption_key,
576            &self.mac_key,
577            IV_SIZE,
578        )
579        .map_err(TvIoError::from)?;
580        Ok(BufWriter::new(Box::new(writer)))
581    }
582
583    fn atomic_read(&self, path: &Path) -> Result<Vec<u8>, OpenReadError> {
584        let data = self.mmap_dir.atomic_read(path)?;
585
586        let mut reader = AesReader::<Aes256Ctr, _>::new::<Hmac<Sha256>>(
587            Cursor::new(data),
588            &self.encryption_key,
589            &self.mac_key,
590            IV_SIZE,
591            MAC_LENGTH,
592        )
593        .map_err(TvIoError::from)?;
594        let mut decrypted = Vec::new();
595
596        reader
597            .read_to_end(&mut decrypted)
598            .map_err(TvIoError::from)?;
599        Ok(decrypted)
600    }
601
602    fn atomic_write(&mut self, path: &Path, data: &[u8]) -> std::io::Result<()> {
603        let mut encrypted = Vec::new();
604        {
605            let mut writer = AesWriter::<Aes256Ctr, Hmac<Sha256>, _>::new(
606                &mut encrypted,
607                &self.encryption_key,
608                &self.mac_key,
609                IV_SIZE,
610            )?;
611            writer.write_all(data)?;
612        }
613
614        self.mmap_dir.atomic_write(path, &encrypted)
615    }
616
617    fn watch(&self, watch_callback: WatchCallback) -> Result<WatchHandle, tantivy::TantivyError> {
618        self.mmap_dir.watch(watch_callback)
619    }
620
621    fn acquire_lock(&self, lock: &Lock) -> Result<DirectoryLock, LockError> {
622        // The lock files aren't encrypted, this is fine since they won't
623        // contain any data. They will be an empty file and a lock will be
624        // placed on them using e.g. flock(2) on macOS and Linux.
625        self.mmap_dir.acquire_lock(lock)
626    }
627}
628
629// This Tantivy trait is used to indicate when no more writes are expected to be
630// done on a writer.
631impl<E: StreamCipher + KeyIvInit, M: Mac + NewMac, W: Write> TerminatingWrite
632    for AesWriter<E, M, W>
633{
634    fn terminate_ref(&mut self, _: AntiCallToken) -> std::io::Result<()> {
635        self.finalize()
636    }
637}
638
639#[cfg(test)]
640use tempfile::tempdir;
641
642#[test]
643fn create_new_store_and_reopen() {
644    let tmpdir = tempdir().unwrap();
645    let dir = EncryptedMmapDirectory::open_or_create(tmpdir.path(), "wordpass", PBKDF_COUNT)
646        .expect("Can't create a new store");
647    drop(dir);
648    let dir = EncryptedMmapDirectory::open(tmpdir.path(), "wordpass")
649        .expect("Can't open the existing store");
650    drop(dir);
651    let dir = EncryptedMmapDirectory::open(tmpdir.path(), "password");
652    assert!(
653        dir.is_err(),
654        "Opened an existing store with the wrong passphrase"
655    );
656}
657
658#[test]
659fn create_store_with_empty_passphrase() {
660    let tmpdir = tempdir().unwrap();
661    let dir = EncryptedMmapDirectory::open(tmpdir.path(), "");
662    assert!(
663        dir.is_err(),
664        "Opened an existing store with the wrong passphrase"
665    );
666}
667
668#[test]
669fn change_passphrase() {
670    let tmpdir = tempdir().unwrap();
671    let dir = EncryptedMmapDirectory::open_or_create(tmpdir.path(), "wordpass", PBKDF_COUNT)
672        .expect("Can't create a new store");
673
674    drop(dir);
675    EncryptedMmapDirectory::change_passphrase(tmpdir.path(), "wordpass", "password", PBKDF_COUNT)
676        .expect("Can't change passphrase");
677    let dir = EncryptedMmapDirectory::open(tmpdir.path(), "wordpass");
678    assert!(
679        dir.is_err(),
680        "Opened an existing store with the old passphrase"
681    );
682    let _ = EncryptedMmapDirectory::open(tmpdir.path(), "password")
683        .expect("Can't open the store with the new passphrase");
684}