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
//! Symmetric encryption module.
//! 
//! This module provides an interface for symmetric (secret key) cryptography. 
//!
//! # Cryptography
//! ## Ratcheting
//! For the n'th message, the keys are hashed n times. The nonce is also hashed. In a system which does not allow for message loss and re-ordering, this provides forward secrecy for the symmetric keys as each key can be destroyed immediately after use. SHA-256 is used because this matches the key length of chacha20.
//!
//! ## Secure Channel
//! Authenticates *first* with HMAC-SHA512 (only the first 256 bytes are used). This was chosen as it is the default authentication mechanism in sodiumoxide.
//! Then we encrypt using ChaCha20. ChaCha20 was chosen over the sodiumoxide default (xsalsa20) because I will not be using a random nonse and chacha is more resistant to crypt analysis (see it's introductory paper). The key is used from the ratcheting system.
//!
//! # Example (Encrypted Authentication)
//! ```
//! # extern crate sodiumoxide;
//! # extern crate proj_crypto;
//! # use proj_crypto::symmetric::*;
//! use sodiumoxide::randombytes;
//! use std::str;
//! 
//! # fn main() {
//! sodiumoxide::init();
//! let message = "hello world!";
//! let k_e = &randombytes::randombytes(32);
//! let k_a = &randombytes::randombytes(32);
//! let message_number: u16 = 0;
//!
//! let mut state = State::new(k_e, k_a);
//! let ciphertext = state.authenticated_encryption(message.as_bytes(), message_number);
//! let plaintext = state.authenticated_decryption(&ciphertext, message_number).unwrap();
//!
//! assert_eq!(message, str::from_utf8(&plaintext).unwrap());
//!
//! // some stuff happens. Now we no-longer need keys for messages numbered less than 8
//!
//! state.increase_iter_to(8);
//!
//! // crypto still works for message numbers starting from 8:
//! let ciphertext8 = state.authenticated_encryption(message.as_bytes(), 8);
//! let plaintext8 = state.authenticated_decryption(&ciphertext8, 8).unwrap();
//!
//! assert_eq!(message, str::from_utf8(&plaintext8).unwrap());
//! # }
//! ```
//!
//! # Example (Plain Authentication)
//! ```
//! # extern crate sodiumoxide;
//! # extern crate proj_crypto;
//! # use proj_crypto::symmetric::*;
//! use sodiumoxide::randombytes;
//! use std::str;
//! 
//! # fn main() {
//! sodiumoxide::init();
//! let message = "hello world!".as_bytes();
//! let k_e = &randombytes::randombytes(32);
//! let k_a = &randombytes::randombytes(32);
//! let message_number: u16 = 0;
//!
//! let mut state = State::new(k_e, k_a);
//! let state = State::new(k_e, k_a);
//! let auth_tag = state.plain_auth_tag(message, message_number);
//!
//! assert!( state.verify_auth_tag(&auth_tag, message, message_number) );
//! # }
//! ```

/*  This file is part of project-crypto.
    project-crypto is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    project-crypto is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with project-crypto.  If not, see http://www.gnu.org/licenses/.*/

mod chacha20hmacsha512256;
mod ratchet;

use self::ratchet::KeyIteration;
use self::chacha20hmacsha512256::ChaCha20HmacSha512256;
use sodiumoxide::crypto::stream::chacha20;
use sodiumoxide::crypto::auth::hmacsha512256;
use sodiumoxide::crypto::hash::sha256;
use sodiumoxide::utils::memzero;
pub use self::chacha20hmacsha512256::AUTH_TAG_BYTES;

/// A wrapper around sha256::Digest so that we can implement Drop on it to clean up the memory when it goes out of scope.
/// This is necessary because often our shared secret keys are sha256 digests.
#[derive(Debug)]
#[derive(PartialEq, Eq)] // equal when all fields are equal. sha256::Digest implements equality in constant time.
pub struct Digest { // used in ratchet and asymmetric
    /// The encapsulated sha256::Digest
    pub digest: sha256::Digest,
}

/// Used in hash_n_times. Unfortunately we can't derive Clone on types with destructors.
impl Clone for Digest {
    fn clone(&self) -> Digest {
        Digest { digest: self.digest.clone() }
    }
}

impl Drop for Digest {
    /// We are using sha256 digests as keys so we need to be careful with them.
    /// This function zeroes out the memory when it goes out of scope
    fn drop(&mut self) {
        let &mut sha256::Digest(ref mut digest_value) = &mut self.digest;
        memzero(digest_value);
    }
}

impl Digest {
    /// returns the actual data of the digest (from inside the sha256::Digest)
    pub fn as_slice(&self) -> [u8; sha256::DIGESTBYTES] {
        let &sha256::Digest(return_val) = &self.digest;

        return_val
    }
}

/// Stores the state of the symmetric encryption system.
/// Memory is zeroed when this goes out of scope
pub struct State {
    encryption_key: KeyIteration, // implements drop()
    authentication_key: KeyIteration, // implements drop()
}

impl State {
    /// Create a new symmetric::State object.
    pub fn new(encryption_key: &[u8], authentication_key: &[u8]) -> State {
        State {
            encryption_key: KeyIteration::first(encryption_key),
            authentication_key: KeyIteration::first(authentication_key),
        }
    }

    /// (private) Creates a new ChaCha20HmacSha512256 object
    fn create_encryption_object(&self, message_number: u16) -> ChaCha20HmacSha512256 {
        // using unwraps because everything was designed to always be the correct length
        let e_k = chacha20::Key::from_slice( &self.encryption_key.nth_key(message_number) ).unwrap();
        let a_k = hmacsha512256::Key::from_slice( &self.authentication_key.nth_key(message_number) ).unwrap();

        let nonce = chacha20::Nonce::from_slice( &hash_message_number(message_number) ).unwrap();

        ChaCha20HmacSha512256::new(e_k, a_k, nonce)
    } // e_k and a_k implement drop so they will clean themselves up. The message number (nonce) is sent in the clear anyway.

    /// Perform authenticated encryption.
    /// The message number is used to select the correct encryption key and as a nonce.
    /// Returns the ciphertext.
    pub fn authenticated_encryption(&self, message: &[u8], message_number: u16) -> Vec<u8> {
        self.create_encryption_object(message_number).authenticate_and_encrypt(message)
    }

    /// Attempt authenticated decryption.
    /// Similar semantics to encryption.
    pub fn authenticated_decryption(&self, ciphertext: &[u8], message_number: u16) -> Option<Vec<u8>> {
        self.create_encryption_object(message_number).decrypt_and_authenticate(ciphertext)
    }

    /// Un-encrypted authentication for verifying public packet metadata such as the message number and length
    pub fn plain_auth_tag(&self, message: &[u8], message_number: u16) -> [u8; hmacsha512256::TAGBYTES] {
        self.create_encryption_object(message_number).plain_auth_tag(message)
    }

    /// for verifying tags created by plain_auth_tag
    pub fn verify_auth_tag(&self, auth_tag: &[u8], message: &[u8], message_number: u16) -> bool {
        // auth_tag is verified in ChaCha20HmacSha512256
        self.create_encryption_object(message_number).verify_auth(auth_tag, message)
    }
 
    /// Destroy keys up to number n.
    /// This is done so that future compromises cannot compromise messages under the older keys and as a performance optimisation to reduce the number of hashes required.
    /// As it cannot be undone, this should not be done until the previous iterations of the keys are no-longer needed: for example their messages have all been acknowledged.
    pub fn increase_iter_to(&mut self, new_n: u16) {
        self.encryption_key.increase_iter_to(new_n);
        self.authentication_key.increase_iter_to(new_n);
    }
       
}

/// hashes the message number to make the nonce (remove all the structure)
fn hash_message_number(num: u16) -> [u8; chacha20::NONCEBYTES] {
    let digest;
    let n = [(num >> 8) as u8, (num & 0xFF) as u8];
    digest = sha256::hash(&n);
    let sha256::Digest(digest_data) = digest;

    // done in a clumsy-looking way so that this doesn't end up being a slice
    let mut ret: [u8; chacha20::NONCEBYTES] = [0; chacha20::NONCEBYTES];

    for i in 0..chacha20::NONCEBYTES {
        ret[i] = digest_data[i];
    }

    ret
}

/******************* Tests *******************/
#[cfg(test)]
mod tests {
    use super::*;
    use sodiumoxide::randombytes;
    extern crate sodiumoxide;
    use std::str;

    const MESSAGE_LENGTH: usize = 1024;

    #[test]
    fn encrypt_decrypt_zero() {
        sodiumoxide::init();
        let message = &randombytes::randombytes(MESSAGE_LENGTH);
        let k_e = &randombytes::randombytes(32);
        let k_a = &randombytes::randombytes(32);
        let message_number: u16 = 0; // important to test the boundary

        let state = State::new(k_e, k_a);
        let ciphertext = state.authenticated_encryption(message, message_number);
        let plaintext = state.authenticated_decryption(&ciphertext, message_number).unwrap();

        assert_eq!(message, &plaintext);
    }

    fn random_message_number() -> u16 {
        let message_number_bytes = randombytes::randombytes(2);
        let mut message_number: u16 = 0;

        // turn two bytes into a u16
        message_number |= ((message_number_bytes[0]) as u16) << 8;
        message_number |= (message_number_bytes[1]) as u16;

        // check the message number is valid
        if message_number == u16::max_value() {
            random_message_number() // generate a new one
        } else {
            message_number // this one is good enough
        }
    }

// this might look like it would be slower but my i5-3570k can do 2 971 550 SHA512 hashes of this size in one second (openssl speed). This is a lot more than u16::max_value()
    #[test]
    fn encrypt_decrypt_random() {
        sodiumoxide::init();
        let message = &randombytes::randombytes(MESSAGE_LENGTH);
        let k_e = &randombytes::randombytes(32);
        let k_a = &randombytes::randombytes(32);
        let message_number = random_message_number();

        let state = State::new(k_e, k_a);
        let ciphertext = state.authenticated_encryption(message, message_number);
        let plaintext = state.authenticated_decryption(&ciphertext, message_number).unwrap();

        assert_eq!(message, &plaintext);
    }

    #[test]
    fn plain_auth_random() {
        sodiumoxide::init();
        let message = &randombytes::randombytes(MESSAGE_LENGTH);
        let k_e = &randombytes::randombytes(32);
        let k_a = &randombytes::randombytes(32);
        let message_number = random_message_number();

        let state = State::new(k_e, k_a);
        let auth_tag = state.plain_auth_tag(message, message_number);

        assert!( state.verify_auth_tag(&auth_tag, message, message_number) );
    }
}