1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

// This is the *local* encryption support - it has nothing to do with the
// encryption used by sync.

// For context, what "local encryption" means in this context is:
// * We use regular sqlite, but want to ensure the credit-card numbers are
//   encrypted in the DB - so we store the number encrypted, and the key
//   is managed by the app.
// * The credit-card API always just accepts and returns the encrypted string,
//   so we also expose encryption and decryption public functions that take
//   the key and text. The core storage API never knows the unencrypted number.
//
// This makes life tricky for Sync - sync has its own encryption and its own
// management of sync keys. The entire records are encrypted on the server -
// so the record on the server has the plain-text number (which is then
// encrypted as part of the entire record), so:
// * When transforming a record from the DB into a Sync record, we need to
//   *decrypt* the field.
// * When transforming a record from Sync into a DB record, we need to *encrypt*
//   the field.
//
// So Sync needs to know the key etc, and that needs to get passed down
// multiple layers, from the app saying "sync now" all the way down to the
// low level sync code.
// To make life a little easier, we do that via a struct.

use crate::error::*;
use error_support::handle_error;

pub type EncryptorDecryptor = jwcrypto::EncryptorDecryptor<Error>;

// public functions we expose over the FFI (which is why they take `String`
// rather than the `&str` you'd otherwise expect)
#[handle_error(Error)]
pub fn encrypt_string(key: String, cleartext: String) -> ApiResult<String> {
    // It would be nice to have more detailed error messages, but that would require the consumer
    // to pass them in.  Let's not change the API yet.
    EncryptorDecryptor::new(&key)?.encrypt(&cleartext, "single string field")
}

#[handle_error(Error)]
pub fn decrypt_string(key: String, ciphertext: String) -> ApiResult<String> {
    // It would be nice to have more detailed error messages, but that would require the consumer
    // to pass them in.  Let's not change the API yet.
    EncryptorDecryptor::new(&key)?.decrypt(&ciphertext, "single string field")
}

#[handle_error(Error)]
pub fn create_autofill_key() -> ApiResult<String> {
    EncryptorDecryptor::create_key()
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_encrypt() {
        let ed = EncryptorDecryptor::new(&create_autofill_key().unwrap()).unwrap();
        let cleartext = "secret";
        let ciphertext = ed.encrypt(cleartext, "secret").unwrap();
        assert_eq!(ed.decrypt(&ciphertext, "secret").unwrap(), cleartext);
        let ed2 = EncryptorDecryptor::new(&create_autofill_key().unwrap()).unwrap();
        assert!(matches!(
            ed2.decrypt(&ciphertext, "secret"),
            Err(Error::CryptoError(_))
        ));
    }

    #[test]
    fn test_decryption_errors() {
        let ed = EncryptorDecryptor::new(&create_autofill_key().unwrap()).unwrap();
        assert!(matches!(
            ed.decrypt("invalid-ciphertext", "invalid").unwrap_err(),
            Error::CryptoError(_)
        ));
        assert!(matches!(
            ed.decrypt("", "empty").unwrap_err(),
            Error::CryptoError(jwcrypto::EncryptorDecryptorError {
                from: jwcrypto::JwCryptoError::EmptyCyphertext,
                ..
            })
        ));
    }
}