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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#![deny(warnings, clippy::pedantic, clippy::all, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

//! The crypter crate provides Rust and FFI for encryption and decryption using AES-GCM-SIV 256-bits.
//!
//! To enable the C api, the feature `ffi` must be enabled.
//! To enable the WASM api, the feature `wasm` must be enabled.
//! See the [examples](../../blob/master/ffi/examples) for working FFI applications.
//!
//! # Examples
//!
//! ```
//!let pass = "superscret";
//!let payload = "mega ultra safe payload";
//!
//!let encrypted = crypter::encrypt(pass.as_bytes(), payload.as_bytes()).expect("Failed to encrypt");
//!let decrypted = crypter::decrypt(pass.as_bytes(), &encrypted).expect("Failed to decrypt");
//!println!("{}", String::from_utf8(decrypted).expect("Invalid decrypted string"));
//! ```
//!
//! # FFI examples
//! ## C example: [example.c](../../blob/master/ffi/examples/c/example.c)
//! ```c
//! #include <stdio.h>
//! #include <string.h>
//!
//! #include <crypter.h>
//!
//! int main() {
//!   const char *pass = "supersecret";
//!   const char *payload = "mega ultra safe payload";
//!
//!   CrypterCSlice pass_slice = {.ptr = (const unsigned char *)pass,
//!                               .len = strlen(pass)};
//!
//!   CrypterRustSlice encrypted = crypter_encrypt(
//!       pass_slice, (CrypterCSlice){.ptr = (const unsigned char *)payload,
//!                                   .len = strlen(payload)});
//!
//!   CrypterCSlice encrypted_slice = {.ptr = encrypted.ptr, .len = encrypted.len};
//!
//!   CrypterRustSlice decrypted = crypter_decrypt(pass_slice, encrypted_slice);
//!
//!   if (decrypted.ptr) {
//!     for (int i = 0; i < decrypted.len; i++) {
//!       if (decrypted.ptr[i] == 0) {
//!         putchar('0');
//!       } else {
//!         putchar(decrypted.ptr[i]);
//!       }
//!     }
//!     putchar('\n');
//!   } else {
//!     puts("Null return");
//!   }
//!
//!   crypter_free_slice(encrypted);
//!   crypter_free_slice(decrypted);
//! }
//! ```
//!
//! ## Lua example: [example.lua](../../blob/master/ffi/examples/lua/example.lua)
//! ```lua
//! local ffi = require('ffi')
//!
//! ffi.cdef[[
//!   typedef struct Slice { uint8_t * ptr; size_t len; } Slice;
//!   typedef struct RustSlice { uint8_t * ptr; size_t len; size_t capacity; } RustSlice;
//!
//!   RustSlice crypter_encrypt(struct Slice pass, struct Slice payload);
//!   RustSlice crypter_decrypt(struct Slice pass, struct Slice payload);
//! ]]
//!
//! local function slice_from_str(text)
//!   local slice = ffi.new('Slice')
//!
//!   slice.ptr = ffi.cast('uint8_t *', text)
//!   slice.len = string.len(text)
//!   return slice
//! end
//!
//! local function relax_rust_slice(rust_slice)
//!   local slice = ffi.new('Slice')
//!
//!   slice.ptr = rust_slice.ptr
//!   slice.len = rust_slice.len
//!   return slice
//! end
//!
//! crypter = ffi.load('crypter')
//!
//! local pass = slice_from_str('supersecret')
//! local encrypted = crypter.crypter_encrypt(pass, slice_from_str('mega ultra safe payload'))
//! local decrypted = crypter.crypter_decrypt(pass, relax_rust_slice(encrypted))
//!
//! if decrypted.ptr ~= nil then
//!   print(ffi.string(decrypted.ptr, decrypted.len))
//! else
//!   print('Failed roud trip')
//! end
//! ```
//!
//! ## WASM example: [index.html](../../blob/master/ffi/examples/wasm/index.html)
//! ```html
//! <!DOCTYPE html>
//! <html>
//!   <head>
//!     <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
//!     <title>crypter</title>
//!   </head>
//!   <body>
//!     <script type="module">
//!       import init from "./crypter.js";
//!
//!       init("./crypter_bg.wasm").then(() => {
//!         const crypter = import('./crypter.js')
//!         crypter.then(c => {
//!           const encoder = new TextEncoder();
//!           const pass = encoder.encode('supersecret');
//!           const encrypted = c.encrypt(pass, encoder.encode('mega ultra safe payload'));
//!           const decrypted = c.decrypt(pass, encrypted);
//!           console.log('Encrypted: ', new TextDecoder().decode(decrypted));
//!         });
//!       });
//!     </script>
//!   </body>
//! </html>
//! ```

/// Encrypts the payload with AES256 GCM SIV. The iv is randomly generated for each call
///
/// Returns [`None`] if an error occurred.
///
/// # Example
/// ```
/// let pass = "mysecret";
/// let payload = "supersecretpayload";
///
/// let encrypted = crypter::encrypt(pass.as_bytes(), payload.as_bytes());
/// ```
#[must_use]
pub fn encrypt(pass: &[u8], payload: &[u8]) -> Option<Vec<u8>> {
    use aes_gcm_siv::aead::generic_array::GenericArray;
    use aes_gcm_siv::aead::Aead;
    use aes_gcm_siv::aead::KeyInit;

    let nonce = nonce();
    let key = derive_key(pass);
    let cipher = aes_gcm_siv::Aes256GcmSiv::new(GenericArray::from_slice(&key));

    cipher.encrypt(&nonce, payload).ok().map(|mut v| {
        v.extend(&nonce);
        v
    })
}

/// Decrypts the payload with AES256 GCM SIV
///
/// Returns [`None`] if an error occurred.
///
/// # Example
/// ```
/// # fn get_encrypted_payload() -> &'static [u8] { &[] }
/// let pass = "mysecret";
/// let payload = get_encrypted_payload();
///
/// let encrypted = crypter::encrypt(pass.as_bytes(), payload);
/// ```
#[must_use]
pub fn decrypt(pass: &[u8], payload: &[u8]) -> Option<Vec<u8>> {
    use aes_gcm_siv::aead::generic_array::GenericArray;
    use aes_gcm_siv::aead::Aead;
    use aes_gcm_siv::aead::KeyInit;

    let nonce = aes_gcm_siv::Nonce::from_slice(&payload[payload.len() - 12..]);
    let key = derive_key(pass);
    let cipher = aes_gcm_siv::Aes256GcmSiv::new(GenericArray::from_slice(&key));

    cipher.decrypt(nonce, &payload[..payload.len() - 12]).ok()
}

fn derive_key(pass: &[u8]) -> aes_gcm_siv::Key<aes_gcm_siv::Aes256GcmSiv> {
    use sha2::Digest;

    let mut hasher = sha2::Sha256::new();
    hasher.update(pass);
    hasher.finalize()
}

fn nonce() -> aes_gcm_siv::Nonce {
    let mut nonce = [0; 12];
    aes_gcm_siv::aead::rand_core::RngCore::fill_bytes(&mut aes_gcm_siv::aead::OsRng, &mut nonce);
    aes_gcm_siv::Nonce::from(nonce)
}

#[cfg(feature = "ffi")]
pub mod ffi {

    macro_rules! try_slice {
        ($slice:expr) => {
            if let Some(slice) = $slice {
                slice
            } else {
                return CrypterRustSlice::null();
            }
        };
    }

    /// Represents a slice of bytes owned by the caller
    #[repr(C)]
    #[derive(Copy, Clone, Debug)]
    pub struct CrypterCSlice<'a> {
        ptr: *const u8,
        len: usize,
        _lifetime: std::marker::PhantomData<&'a ()>,
    }

    impl<'a> From<CrypterCSlice<'a>> for Option<&'a [u8]> {
        fn from(slice: CrypterCSlice<'a>) -> Self {
            if slice.ptr.is_null() {
                None
            } else {
                Some(unsafe { std::slice::from_raw_parts(slice.ptr, slice.len) })
            }
        }
    }

    /// Represents a slice of bytes owned by Rust
    ///
    /// # Safety
    ///
    /// To free the memory [`crypter_free_slice`](fn.crypter_free_slice.html) must be called
    #[repr(C)]
    #[derive(Copy, Clone, Debug)]
    pub struct CrypterRustSlice {
        ptr: *mut u8,
        len: usize,
        capacity: usize,
    }

    impl CrypterRustSlice {
        #[must_use]
        pub fn null() -> Self {
            Self {
                ptr: std::ptr::null_mut(),
                len: 0,
                capacity: 0,
            }
        }
    }

    /// Frees the slice of bytes owned by Rust
    ///
    /// # Safety
    ///
    /// It may be unsafe to call `free()` on this slice as there is no guarantee of which allocator
    /// was used
    #[no_mangle]
    pub extern "C" fn crypter_free_slice(slice: CrypterRustSlice) {
        if !slice.ptr.is_null() {
            std::mem::drop(unsafe { Vec::from_raw_parts(slice.ptr, slice.len, slice.capacity) });
        }
    }

    // TODO: Use Vec::into_raw_parts() when available
    impl From<Vec<u8>> for CrypterRustSlice {
        fn from(mut vec: Vec<u8>) -> Self {
            let rust_slice = Self {
                ptr: vec.as_mut_ptr(),
                len: vec.len(),
                capacity: vec.capacity(),
            };
            std::mem::forget(vec);
            rust_slice
        }
    }

    /// Encrypts the payload with AES256 GCM SIV. The iv is randomly generated for each call
    ///
    /// A wrapper around [`encrypt`](../fn.encrypt.html)
    ///
    /// # Safety
    ///
    /// This method does not take ownership of the parameters
    #[no_mangle]
    pub extern "C" fn crypter_encrypt<'a>(
        pass: CrypterCSlice<'a>,
        payload: CrypterCSlice<'a>,
    ) -> CrypterRustSlice {
        let pass = try_slice!(pass.into());
        let payload = try_slice!(payload.into());
        super::encrypt(pass, payload).map_or_else(CrypterRustSlice::null, CrypterRustSlice::from)
    }

    /// Decrypts the payload with AES256 GCM SIV
    ///
    /// A wrapper around [`decrypt`](../fn.decrypt.html)
    ///
    /// # Safety
    ///
    /// This method does not take ownership of the parameters
    #[no_mangle]
    pub extern "C" fn crypter_decrypt<'a>(
        pass: CrypterCSlice<'a>,
        payload: CrypterCSlice<'a>,
    ) -> CrypterRustSlice {
        let pass = try_slice!(pass.into());
        let payload = try_slice!(payload.into());
        super::decrypt(pass, payload).map_or_else(CrypterRustSlice::null, CrypterRustSlice::from)
    }
}

#[cfg(feature = "wasm")]
pub mod wasm {
    use wasm_bindgen::prelude::*;

    #[wasm_bindgen]
    #[must_use]
    pub fn encrypt(pass: &[u8], payload: &[u8]) -> Option<Vec<u8>> {
        super::encrypt(pass, payload)
    }

    #[wasm_bindgen]
    #[must_use]
    #[allow(clippy::option_if_let_else)]
    pub fn decrypt(pass: &[u8], payload: &[u8]) -> Option<Vec<u8>> {
        super::decrypt(pass, payload)
    }
}

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

    #[test]
    fn round_trip() {
        let pass = "secret_string";
        let payload = "super secret payload";

        let encrypted = encrypt(pass.as_bytes(), payload.as_bytes()).unwrap();
        let decrypted = decrypt(pass.as_bytes(), &encrypted).unwrap();

        let recovered = String::from_utf8(decrypted).unwrap();

        assert_eq!(recovered, payload);
    }

    #[test]
    fn corrupted_byte() {
        let pass = "secret_string";
        let payload = "super secret payload";

        let encrypted = encrypt(pass.as_bytes(), payload.as_bytes()).unwrap();

        for i in 0..encrypted.len() {
            let mut corrupted = encrypted.clone();
            corrupted[i] = !corrupted[i];
            assert_eq!(decrypt(pass.as_bytes(), &corrupted), None);
        }
    }
}