lime_double_ratchet.cpp 32.7 KB
Newer Older
johan's avatar
johan committed
1 2
/*
	lime_double_ratchet.cpp
johan's avatar
johan committed
3 4
	@author Johan Pascal
	@copyright	Copyright (C) 2017  Belledonne Communications SARL
johan's avatar
johan committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

	This program 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.

	This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

Johan Pascal's avatar
Johan Pascal committed
20
#include "lime_log.hpp"
johan's avatar
johan committed
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
#include "lime_double_ratchet.hpp"
#include "lime_double_ratchet_protocol.hpp"
#include "lime_localStorage.hpp"

#include "bctoolbox/exception.hh"

#include <algorithm> //copy_n


using namespace::std;
using namespace::lime;

namespace lime {
	/****************************************************************************/
	/* Helpers functions not part of DR class                                   */
	/****************************************************************************/
	/* Key derivation functions : KDF_RK (root key derivation function, for DH ratchet) and KDF_CK(chain key derivation function, for symmetric ratchet) */
	/**
	 * @Brief Key Derivation Function used in Root key/Diffie-Hellman Ratchet chain.
40
	 *      Use HKDF (see RFC5869) to derive CK and RK in one derivation
johan's avatar
johan committed
41
	 *
42
	 * @param[in,out]	RK	Input buffer used as salt also to store the 32 first byte of output key material
johan's avatar
johan committed
43
	 * @param[out]		CK	Output buffer, last 32 bytes of output key material
44
	 * @param[in]		dh_out	Buffer used as input key material
johan's avatar
johan committed
45 46
	 */
	template <typename Curve>
47
	static void KDF_RK(DRChainKey &RK, DRChainKey &CK, const X<Curve, lime::Xtype::sharedSecret> &dh_out) noexcept {
48
		// Ask for twice the size of a DRChainKey for HKDF output
49
		lime::sBuffer<2*lime::settings::DRChainKeySize> HKDFoutput;
50 51 52 53 54 55
		HMAC_KDF<SHA512>(RK.data(), RK.size(), dh_out.data(), dh_out.size(), lime::settings::hkdf_DRChainKey_info, HKDFoutput.data(), HKDFoutput.size());

		// First half of the output goes to RootKey (RK)
		std::copy_n(HKDFoutput.cbegin(), lime::settings::DRChainKeySize, RK.begin());
		// Second half of the output goes to ChainKey (CK)
		std::copy_n(HKDFoutput.cbegin()+lime::settings::DRChainKeySize, lime::settings::DRChainKeySize, CK.begin());
johan's avatar
johan committed
56 57
	}

58 59 60 61
	/* Set of constants used as input of HKDF like function, see double ratchet spec section 5.2 - KDF_CK */
	const std::array<std::uint8_t,1> hkdf_ck_info{{0x02}};
	const std::array<std::uint8_t,1> hkdf_mk_info{{0x01}};

johan's avatar
johan committed
62 63
	/**
	 * @Brief Key Derivation Function used in Symmetric key ratchet chain.
64
	 *      Implemented according to DR spec section 5.2 using HMAC-SHA512
johan's avatar
johan committed
65
	 *		MK = HMAC-SHA512(CK, hkdf_mk_info) // get 48 bytes of it: first 32 to be key and last 16 to be IV
66
	 *		CK = HMAC-SHA512(CK, hkdf_ck_info)
67
	 *              hkdf_ck_info and hldf_mk_info being a distincts constants (0x02 and 0x01 as suggested in double ratchet - section 5.2)
johan's avatar
johan committed
68
	 *
69
	 * @param[in,out]	CK	Input/output buffer used as key to compute MK and then next CK
70
	 * @param[out]		MK	Message Key(32 bytes) and IV(16 bytes) computed from HMAC_SHA512 keyed with CK
johan's avatar
johan committed
71 72 73
	 */
	static void KDF_CK(DRChainKey &CK, DRMKey &MK) noexcept {
		// derive MK and IV from CK and constant
74
		HMAC<SHA512>(CK.data(), CK.size(), hkdf_mk_info.data(), hkdf_mk_info.size(), MK.data(), MK.size());
johan's avatar
johan committed
75 76

		// use temporary buffer, not likely that output and key could be the same buffer
77
		DRChainKey tmp;
78
		HMAC<SHA512>(CK.data(), CK.size(), hkdf_ck_info.data(), hkdf_ck_info.size(), tmp.data(), tmp.size());
79
		CK = tmp;
johan's avatar
johan committed
80 81 82 83 84 85 86 87 88
	}

	/**
	 * @brief Decrypt as described is spec section 3.1
	 *
	 * @param[in]	MK		A buffer holding key<32 bytes> || IV<16 bytes>
	 * @param[in]	ciphertext	buffer holding: header<size depends on DHKey type> || ciphertext || auth tag<16 bytes>
	 * @param[in]	headerSize	Size of the header included in ciphertext
	 * @param[in]	AD		Associated data
89
	 * @param[out]	plaintext	the output message : a vector resized to hold the plaintext.
johan's avatar
johan committed
90 91 92 93
	 *
	 * @return false if authentication failed
	 *
	 */
94
	static bool decrypt(const lime::DRMKey &MK, const std::vector<uint8_t> &ciphertext, const size_t headerSize, std::vector<uint8_t> &AD, std::vector<uint8_t> &plaintext) {
95
		plaintext.resize(ciphertext.size() - headerSize - lime::settings::DRMessageAuthTagSize); // size of plaintext is: cipher - header - authentication tag, we're getting a vector, we must resize it
96 97 98 99 100 101
		return AEAD_decrypt<AES256GCM>(MK.data(), lime::settings::DRMessageKeySize, // MK buffer hold key<DRMessageKeySize bytes>||IV<DRMessageIVSize bytes>
					MK.data()+lime::settings::DRMessageKeySize, lime::settings::DRMessageIVSize,
					ciphertext.data()+headerSize, plaintext.size(), // cipher text starts after header, length is the one computed for plaintext
					AD.data(), AD.size(),
					ciphertext.data()+ciphertext.size() - lime::settings::DRMessageAuthTagSize, lime::settings::DRMessageAuthTagSize, // tag is in the last 16 bytes of buffer
					plaintext.data());
johan's avatar
johan committed
102
	}
103 104 105 106 107 108 109 110
	// when plaintext is a fixed size buffer, no need to resize it
	static bool decrypt(const lime::DRMKey &MK, const std::vector<uint8_t> &ciphertext, const size_t headerSize, std::vector<uint8_t> &AD, sBuffer<lime::settings::DRrandomSeedSize> &plaintext) {
		return AEAD_decrypt<AES256GCM>(MK.data(), lime::settings::DRMessageKeySize, // MK buffer hold key<DRMessageKeySize bytes>||IV<DRMessageIVSize bytes>
					MK.data()+lime::settings::DRMessageKeySize, lime::settings::DRMessageIVSize,
					ciphertext.data()+headerSize, plaintext.size(), // cipher text starts after header, length is the one computed for plaintext
					AD.data(), AD.size(),
					ciphertext.data()+ciphertext.size() - lime::settings::DRMessageAuthTagSize, lime::settings::DRMessageAuthTagSize, // tag is in the last 16 bytes of buffer
					plaintext.data());
johan's avatar
johan committed
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
	}

	/****************************************************************************/
	/* DR member functions                                                      */
	/****************************************************************************/

	/****************************************************************************/
	/* DR class constructors: 3 cases                                           */
	/*     - sender's init                                                      */
	/*     - receiver's init                                                    */
	/*     - initialisation from session stored in local storage                */
	/****************************************************************************/

	/**
	 * @brief Create a new DR session for sending message. Match pseudo code for RatchetInitAlice in DR spec section 3.3
	 *
	 * @param[in]	localStorage		Local storage accessor to save DR session and perform mkskipped lookup
	 * @param[in]	SK			a 32 bytes shared secret established prior the session init (likely done using X3DH)
johan's avatar
johan committed
129
	 * @param[in]	AD			The associated data generated by X3DH protocol and permanently part of the DR session(see X3DH spec section 3.3 and lime doc section 5.4.3)
johan's avatar
johan committed
130 131
	 * @param[in]	peerPublicKey		the public key of message recipient (also obtained through X3DH, shall be peer SPk)
	 * @param[in]	peerDid			Id used in local storage for this peer Device this session shall be attached to
132
	 * @param[in]	selfDid			Id used in local storage for local user this session shall be attached to
johan's avatar
johan committed
133
	 * @param[in]	X3DH_initMessage	at session creation as sender we shall also store the X3DHInit message to be able to include it in all message until we got a response from peer
johan's avatar
johan committed
134
	 * @param[in]	RNG_context		A Random Number Generator context used for any rndom generation needed by this session
johan's avatar
johan committed
135 136
	 */
	template <typename Curve>
137
	DR<Curve>::DR(lime::Db *localStorage, const DRChainKey &SK, const SharedADBuffer &AD, const X<Curve, lime::Xtype::publicKey> &peerPublicKey, long int peerDid, long int selfDid, const std::vector<uint8_t> &X3DH_initMessage, std::shared_ptr<RNG> RNG_context)
johan's avatar
johan committed
138
	:m_DHr{peerPublicKey},m_DHr_valid{true}, m_DHs{},m_RK(SK),m_CKs{},m_CKr{},m_Ns(0),m_Nr(0),m_PN(0),m_sharedAD(AD),m_mkskipped{},
139
	m_RNG{RNG_context},m_dbSessionId{0},m_usedNr{0},m_usedDHid{0},m_localStorage{localStorage},m_dirty{DRSessionDbStatus::dirty},m_peerDid{peerDid}, m_db_Uid{selfDid},
johan's avatar
johan committed
140 141 142
	m_active_status{true}, m_X3DH_initMessage{X3DH_initMessage}
	{
		// generate a new self key pair
143 144
		auto DH = make_keyExchange<Curve>();
		DH->createKeyPair(m_RNG);
johan's avatar
johan committed
145 146

		// copy the peer public key into ECDH context
147 148
		DH->set_peerPublic(peerPublicKey);

johan's avatar
johan committed
149
		// get self key pair from context
150 151
		m_DHs.publicKey() = DH->get_selfPublic();
		m_DHs.privateKey() = DH->get_secret();
johan's avatar
johan committed
152 153

		// compute shared secret
154
		DH->computeSharedSecret();
johan's avatar
johan committed
155 156

		// derive the root key
157
		KDF_RK<Curve>(m_RK, m_CKs, DH->get_sharedSecret());
johan's avatar
johan committed
158 159 160 161 162 163 164
	}

	/**
	 * @brief Create a new DR session for message reception. Match pseudo code for RatchetInitBob in DR spec section 3.3
	 *
	 * @param[in]	localStorage	Local storage accessor to save DR session and perform mkskipped lookup
	 * @param[in]	SK		a 32 bytes shared secret established prior the session init (likely done using X3DH)
johan's avatar
johan committed
165
	 * @param[in]	AD		The associated data generated by X3DH protocol and permanently part of the DR session(see X3DH spec section 3.3 and lime doc section 5.4.3)
johan's avatar
johan committed
166 167
	 * @param[in]	selfKeyPair	the key pair used by sender to establish this DR session
	 * @param[in]	peerDid		Id used in local storage for this peer Device this session shall be attached to
johan's avatar
johan committed
168 169
	 * @param[in]	selfDid		Id used in local storage for local user this session shall be attached to
	 * @param[in]	RNG_context	A Random Number Generator context used for any rndom generation needed by this session
johan's avatar
johan committed
170 171
	 */
	template <typename Curve>
172
	DR<Curve>::DR(lime::Db *localStorage, const DRChainKey &SK, const SharedADBuffer &AD, const Xpair<Curve> &selfKeyPair, long int peerDid, long int selfDid, std::shared_ptr<RNG> RNG_context)
johan's avatar
johan committed
173
	:m_DHr{},m_DHr_valid{false},m_DHs{selfKeyPair},m_RK(SK),m_CKs{},m_CKr{},m_Ns(0),m_Nr(0),m_PN(0),m_sharedAD(AD),m_mkskipped{},
174
	m_RNG{RNG_context},m_dbSessionId{0},m_usedNr{0},m_usedDHid{0},m_localStorage{localStorage},m_dirty{DRSessionDbStatus::dirty},m_peerDid{peerDid}, m_db_Uid{selfDid},
johan's avatar
johan committed
175 176 177 178 179 180 181 182 183 184
	m_active_status{true}, m_X3DH_initMessage{}
	{ }

	/**
	 *  @brief Create a new DR session to be loaded from db,
	 *  m_dirty is already set to clean and DHR_valid to true as we won't save a session if no successfull sending or reception was performed
	 *  if loading fails, caller should destroy the session
	 *
	 * @param[in]	localStorage	Local storage accessor to save DR session and perform mkskipped lookup
	 * @param[in]	sessionId	row id in the database identifying the session to be loaded
johan's avatar
johan committed
185
	 * @param[in]	RNG_context	A Random Number Generator context used for any rndom generation needed by this session
johan's avatar
johan committed
186 187
	 */
	template <typename Curve>
188
	DR<Curve>::DR(lime::Db *localStorage, long sessionId, std::shared_ptr<RNG> RNG_context)
johan's avatar
johan committed
189
	:m_DHr{},m_DHr_valid{true},m_DHs{},m_RK{},m_CKs{},m_CKr{},m_Ns(0),m_Nr(0),m_PN(0),m_sharedAD{},m_mkskipped{},
190
	m_RNG{RNG_context},m_dbSessionId{sessionId},m_usedNr{0},m_usedDHid{0},m_localStorage{localStorage},m_dirty{DRSessionDbStatus::clean},m_peerDid{0}, m_db_Uid{0},
johan's avatar
johan committed
191 192 193 194 195 196
	m_active_status{false}, m_X3DH_initMessage{}
	{
		session_load();
	}

	template <typename Curve>
197
	DR<Curve>::~DR() { }
johan's avatar
johan committed
198 199 200 201 202 203 204 205 206 207 208

	/**
	 * @brief Derive chain keys until reaching the requested Id. Handling unordered messages
	 *	Store the derived but not used keys in a list indexed by peer DH and Nr
	 *
	 *	@param[in]	until	index we must reach in that chain key
	 *	@param[in]	limit	maximum number of allowed derivations
	 *
	 *	@throws		when we try to overpass the maximum number of key derivation since last valid message
	 */
	template <typename Curve>
209
	void DR<Curve>::skipMessageKeys(const uint16_t until, const int limit) {
johan's avatar
johan committed
210 211 212 213 214 215 216 217
		if (m_Nr==until) return; // just to be sure we actually have MK to derive and store

		// check if there are not too much message keys to derive in this chain
		if (m_Nr + limit < until) {
			throw BCTBX_EXCEPTION << "DR Session is too far behind this message to derive requested amount of keys: "<<(until-m_Nr);
		}

		// each call to this function is made with a different DHr
Johan Pascal's avatar
Johan Pascal committed
218
		ReceiverKeyChain<Curve> newRChain{m_DHr};
johan's avatar
johan committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
		m_mkskipped.push_back(newRChain);
		auto rChain = &m_mkskipped.back();

		DRMKey MK;
		while (m_Nr<until) {
			KDF_CK(m_CKr, MK);
			// insert the nessage key into the list of skipped ones
			rChain->messageKeys[m_Nr]=MK;

			m_Nr++;
		}
	}

	/**
	 * @brief perform a Diffie-Hellman Ratchet as described in DR spec section 3.5
	 *
	 * @param[in] headerDH	The peer public key to use for the DH shared secret computation
	 */
	template <typename Curve>
238
	void DR<Curve>::DHRatchet(const X<Curve, lime::Xtype::publicKey> &headerDH) {
johan's avatar
johan committed
239 240 241 242 243 244 245 246
		// reinit counters
		m_PN=m_Ns;
		m_Ns=0;
		m_Nr=0;

		// this is our new DHr
		m_DHr = headerDH;

247 248 249 250 251 252 253
		// generate a new self key pair
		auto DH = make_keyExchange<Curve>();

		// copy the peer public key into ECDH context
		DH->set_peerPublic(m_DHr);
		DH->set_selfPublic(m_DHs.publicKey());
		DH->set_secret(m_DHs.privateKey());
johan's avatar
johan committed
254 255

		//  Derive the new receiving chain key
256 257
		DH->computeSharedSecret();
		KDF_RK<Curve>(m_RK, m_CKr, DH->get_sharedSecret());
johan's avatar
johan committed
258 259

		// generate a new self key pair
260
		DH->createKeyPair(m_RNG);
johan's avatar
johan committed
261

262 263 264
		//  Derive the new sending chain key
		DH->computeSharedSecret();
		KDF_RK<Curve>(m_RK, m_CKs, DH->get_sharedSecret());
johan's avatar
johan committed
265

266 267 268
		// get self key pair from context
		m_DHs.publicKey() = DH->get_selfPublic();
		m_DHs.privateKey() = DH->get_secret();
johan's avatar
johan committed
269 270 271 272 273 274 275 276

		// modified the DR session, not in sync anymore with local storage
		m_dirty = DRSessionDbStatus::dirty_ratchet;
	}

	/**
	 * @brief Encrypt using the double-ratchet algorithm.
	 *
277
	 * @param[in]	plaintext			the input to be encrypted, may actually be a 32 bytes buffer holding the seed used to generate key+IV for a AES-GCM encryption to the actual message
278 279 280
	 * @param[in]	AD				Associated Data, this buffer shall hold: source GRUU<...> || recipient GRUU<...> || [ actual message AEAD auth tag OR recipient User Id]
	 * @param[out]	ciphertext			buffer holding the header, cipher text and auth tag, shall contain the key and IV used to cipher the actual message, auth tag applies on AD || header
	 * @param[in]	payloadDirectEncryption		A flag to set in message header: set when having payload in the DR message
johan's avatar
johan committed
281 282
	 */
	template <typename Curve>
283 284
	template <typename inputContainer> // input container can be a sBuffer (fixed size) holding a random seed or std::vector<uint8_t> holding the actual message
	void DR<Curve>::ratchetEncrypt(const inputContainer &plaintext, std::vector<uint8_t> &&AD, std::vector<uint8_t> &ciphertext, const bool payloadDirectEncryption) {
johan's avatar
johan committed
285 286 287 288 289 290
		m_dirty = DRSessionDbStatus::dirty_encrypt; // we're about to modify this session, it won't be in sync anymore with local storage
		// chain key derivation(also compute message key)
		DRMKey MK;
		KDF_CK(m_CKs, MK);

		// build header string in the ciphertext buffer
291
		double_ratchet_protocol::buildMessage_header(ciphertext, m_Ns, m_PN, m_DHs.publicKey(), m_X3DH_initMessage, payloadDirectEncryption);
johan's avatar
johan committed
292 293 294 295 296 297
		auto headerSize = ciphertext.size(); // cipher text holds only the DR header for now

		// increment current sending chain message index
		m_Ns++;

		// build AD: given AD || sharedAD stored in session || header (see DR spec section 3.4)
Johan Pascal's avatar
Johan Pascal committed
298 299
		AD.insert(AD.end(), m_sharedAD.cbegin(), m_sharedAD.cend());
		AD.insert(AD.end(), ciphertext.cbegin(), ciphertext.cend()); // cipher text holds header only for now
johan's avatar
johan committed
300 301 302 303 304

		// data will be written directly in the underlying structure by C library, so set size to the actual one
		// header size + cipher text size + auth tag size
		ciphertext.resize(ciphertext.size()+plaintext.size()+lime::settings::DRMessageAuthTagSize);

305 306 307 308 309 310
		AEAD_encrypt<AES256GCM>(MK.data(), lime::settings::DRMessageKeySize, // MK buffer also hold the IV
				MK.data()+lime::settings::DRMessageKeySize, lime::settings::DRMessageIVSize, // IV is stored in the same buffer as key, after it
				plaintext.data(), plaintext.size(),
				AD.data(), AD.size(),
				ciphertext.data()+headerSize+plaintext.size(), lime::settings::DRMessageAuthTagSize, // directly store tag after cipher text in the output buffer
				ciphertext.data()+headerSize);
311 312 313 314 315 316 317

		if (m_Ns >= lime::settings::maxSendingChain) { // if we reached maximum encryption wuthout DH ratchet step, session becomes inactive
			m_active_status = false;
		}

		if (session_save() == true) {
			m_dirty = DRSessionDbStatus::clean; // this session and local storage are back in sync
johan's avatar
johan committed
318 319 320 321 322 323
		}
	}


	/**
	 * @brief Decrypt Double Ratchet message
324 325 326 327 328 329 330
	 *
	 * @param[in]	ciphertext			Input to be decrypted, is likely to be a 32 bytes vector holding the crypted version of a random seed
	 * @param[in]	AD				Associated data authenticated along the encryption (initial session AD and DR message header are append to it)
	 * @param[out]	plaintext			Decrypted output
	 * @param[in]	payloadDirectEncryption		A flag to enforce checking on message type: when set we expect to get payload in the message(so message header matching flag must be set)
	 *
	 * @return	true on success
johan's avatar
johan committed
331 332
	 */
	template <typename Curve>
333 334
	template <typename outputContainer> // output container can be a sBuffer (fixed size) getting a random seed or std::vector<uint8_t> getting the actual message
	bool DR<Curve>::ratchetDecrypt(const std::vector<uint8_t> &ciphertext,const std::vector<uint8_t> &AD, outputContainer &plaintext, const bool payloadDirectEncryption) {
johan's avatar
johan committed
335 336 337 338 339 340
		// parse header
		double_ratchet_protocol::DRHeader<Curve> header{ciphertext};
		if (!header.valid()) { // check it is valid otherwise just stop
			throw BCTBX_EXCEPTION << "DR Session got an invalid message header";
		}

341 342 343 344 345
		// check the header match what we are expecting in the message: actual payload or random seed(it shall be set in the message header)
		if (payloadDirectEncryption != header.payloadDirectEncryption()) {
			throw BCTBX_EXCEPTION << "DR packet header direct encryption flag ("<<(header.payloadDirectEncryption()?"true":"false")<<") not in sync with caller request("<<(payloadDirectEncryption?"true":"false")<<")";
		}

johan's avatar
johan committed
346 347
		// build an Associated Data buffer: given AD || shared AD stored in session || header (as in DR spec section 3.4)
		std::vector<uint8_t> DRAD{AD}; // copy given AD
Johan Pascal's avatar
Johan Pascal committed
348 349
		DRAD.insert(DRAD.end(), m_sharedAD.cbegin(), m_sharedAD.cend());
		DRAD.insert(DRAD.end(), ciphertext.cbegin(), ciphertext.cbegin()+header.size());
johan's avatar
johan committed
350 351

		DRMKey MK;
352
		int maxAllowedDerivation = lime::settings::maxMessageSkip;
johan's avatar
johan committed
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
		m_dirty = DRSessionDbStatus::dirty_decrypt; // we're about to modify the DR session, it will not be in sync anymore with local storage
		if (!m_DHr_valid) { // it's the first message arriving after the initialisation of the chain in receiver mode, we have no existing history in this chain
			DHRatchet(header.DHs()); // just perform the DH ratchet step
			m_DHr_valid=true;
		} else {
			// check stored message keys
			if (trySkippedMessageKeys(header.Ns(), header.DHs(), MK)) {
				if (decrypt(MK, ciphertext, header.size(), DRAD, plaintext) == true) {
					//Decrypt went well, we must save the session to DB
					if (session_save() == true) {
						m_dirty = DRSessionDbStatus::clean; // this session and local storage are back in sync
						m_usedDHid=0; // reset variables used to tell the local storage to delete them
						m_usedNr=0;
						m_X3DH_initMessage.clear(); // just in case we had a valid X3DH init in session, erase it as it's not needed after the first message received from peer
					}
				} else {
					return false;
				};
				return true;
			}
			// if header DH public key != current stored peer public DH key: we must perform a DH ratchet
			if (m_DHr!=header.DHs()) {
				maxAllowedDerivation -= header.PN()-m_Nr; /* we must derive headerPN-Nr keys, remove this from the count of our allowedDerivation number */
				skipMessageKeys(header.PN(), lime::settings::maxMessageSkip-header.Ns()); // we must keep header.Ns derivations available for the next chain
				DHRatchet(header.DHs());
			}
		}

		// in the derivation limit we remove the derivation done in the previous DH rachet key chain
382
		skipMessageKeys(header.Ns(), maxAllowedDerivation);
johan's avatar
johan committed
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411

		// generate key material for decryption(and derive Chain key)
		KDF_CK(m_CKr, MK);

		m_Nr++;

		//decrypt and save on succes
		if (decrypt(MK, ciphertext, header.size(), DRAD, plaintext) == true ) {
			if (session_save() == true) {
				m_dirty = DRSessionDbStatus::clean; // this session and local storage are back in sync
				m_mkskipped.clear(); // potential skipped message keys are now stored in DB, clear the local storage
				m_X3DH_initMessage.clear(); // just in case we had a valid X3DH init in session, erase it as it's not needed after the first message received from peer
			}
			return true;
		} else {
			return false;
		}
	}

	/* template instanciations for DHKeyX25519 and DHKeyX448 */
#ifdef EC25519_ENABLED
	template class DR<C255>;
#endif

#ifdef EC448_ENABLED
	template class DR<C448>;
#endif

	template <typename Curve>
Johan Pascal's avatar
Johan Pascal committed
412
	void encryptMessage(std::vector<RecipientInfos<Curve>>& recipients, const std::vector<uint8_t>& plaintext, const std::string& recipientUserId, const std::string& sourceDeviceId, std::vector<uint8_t>& cipherMessage, const lime::EncryptionPolicy encryptionPolicy) {
413 414 415 416 417 418 419 420 421 422 423
		// Shall we set the payload in the DR message or in a separate cupher message buffer?
		bool payloadDirectEncryption;
		switch (encryptionPolicy) {
			case lime::EncryptionPolicy::DRMessage:
				payloadDirectEncryption = true;
				break;

			case lime::EncryptionPolicy::cipherMessage:
				payloadDirectEncryption = false;
				break;

424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
			case lime::EncryptionPolicy::optimizeGlobalBandwidth:
				// optimize the global bandwith consumption: upload size to server + donwload size from server to recipient
				// server is considered to act cleverly and select the DR message to be send to server not just forward everything to the recipient for them to sort out which is their part
				// - DR message policy: up and down size are recipient number * plaintext size
				// - cipher message policy : 	up is <plaintext size + authentication tag size>(cipher message size) + recipient number * random seed size
				// 				down is recipient number * (random seed size + <plaintext size + authentication tag size>(the cipher message))
				// Note: We are not taking in consideration the fact that being multipart, the message gets an extra multipart boundary when using cipher message mode
				if ( 2*recipients.size()*plaintext.size() <=
						(plaintext.size() + lime::settings::DRMessageAuthTagSize + (2*lime::settings::DRrandomSeedSize + plaintext.size() + lime::settings::DRMessageAuthTagSize)*recipients.size()) )  {
					payloadDirectEncryption = true;
				} else {
					payloadDirectEncryption = false;
				}
				break;

				break;
			case lime::EncryptionPolicy::optimizeUploadSize:
			default: // to make compiler happy but it shall not be necessary
				// Default encryption policy : go for the optimal upload size. All other parts being equal, size of output data is
443 444
				// - DR message policy:     recipients number * plaintext size (plaintext is present encrypted in each recipient message)
				// - cipher message policy: plaintext size + authentication tag size (the cipher message) + recipients number * random seed size (each DR message holds the random seed as encrypted data)
445
				// Note: We are not taking in consideration the fact that being multipart, the message gets an extra multipart boundary when using cipher message mode
446 447 448 449 450 451 452 453 454 455 456 457 458
				if ( recipients.size()*plaintext.size() <= (plaintext.size() + lime::settings::DRMessageAuthTagSize + (lime::settings::DRrandomSeedSize*recipients.size())) ) {
					payloadDirectEncryption = true;
				} else {
					payloadDirectEncryption = false;
				}
				break;
		}

		/* associated data authenticated by the AEAD scheme used by double ratchet encrypt/decrypt
		 * - Payload in the cipherMessage: auth tag from cipherMessage || source Device Id || recipient Device Id
		 * - Payload in the DR message: recipient User Id || source Device Id || recipient Device Id
		 *   This buffer will store the part common to all recipients and the recipient Device Is is appended when looping on all recipients performing DR encrypt
		 */
459
		std::vector<uint8_t> AD;
460 461

		// used only when payload is not in the DR message
462
		lime::sBuffer<lime::settings::DRrandomSeedSize> randomSeed; // this seed is sent in DR message and used to derivate random key + IV to encrypt the actual message
463 464 465 466 467

		if (!payloadDirectEncryption) { // Payload is encrypted in a separate cipher message buffer while the key used to encrypt it is in the DR message
			// First generate a key and IV, use it to encrypt the given message, Associated Data are : sourceDeviceId || recipientUserId
			// generate the random seed
			auto RNG_context = make_RNG();
johan's avatar
johan committed
468
			RNG_context->randomize(randomSeed);
469 470

			// expansion of randomSeed to 48 bytes: 32 bytes random key + 16 bytes nonce, use HKDF with empty salt
471 472 473
			std::vector<uint8_t> emptySalt;
			emptySalt.clear(); //just to be sure
			lime::sBuffer<lime::settings::DRMessageKeySize+lime::settings::DRMessageIVSize> randomKey;
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
			HMAC_KDF<SHA512>(emptySalt.data(), emptySalt.size(), randomSeed.data(), randomSeed.size(), lime::settings::hkdf_randomSeed_info, randomKey.data(), randomKey.size());

			// resize cipherMessage vector as it is adressed directly by C library: same as plain message + room for the authentication tag
			cipherMessage.resize(plaintext.size()+lime::settings::DRMessageAuthTagSize);

			// AD is source deviceId(gruu) || recipientUserId(sip uri)
			AD.assign(sourceDeviceId.cbegin(),sourceDeviceId.cend());
			AD.insert(AD.end(), recipientUserId.cbegin(), recipientUserId.cend());

			// encrypt to cipherMessage buffer
			AEAD_encrypt<AES256GCM>(randomKey.data(), lime::settings::DRMessageKeySize, // key buffer also hold the IV
				randomKey.data()+lime::settings::DRMessageKeySize, lime::settings::DRMessageIVSize, // IV is stored in the same buffer as key, after it
				plaintext.data(), plaintext.size(),
				AD.data(), AD.size(),
				cipherMessage.data()+plaintext.size(), lime::settings::DRMessageAuthTagSize, // directly store tag after cipher text in the output buffer
				cipherMessage.data());

			// Associated Data to Double Ratchet encryption is: auth tag of cipherMessage AEAD || sourceDeviceId || recipient device Id(gruu)
			// build the common part to AD given to DR Session encryption
			AD.assign(cipherMessage.cbegin()+plaintext.size(), cipherMessage.cend());
		} else { // Payload is directly encrypted in the DR message
			AD.assign(recipientUserId.cbegin(), recipientUserId.cend());
			cipherMessage.clear(); // be sure no cipherMessage is produced
		}
		/* complete AD, it now holds:
		 * - Payload in the cipherMessage: auth tag from cipherMessage || source Device Id
		 * - Payload in the DR message: recipient User Id || source Device Id
		 */
Johan Pascal's avatar
Johan Pascal committed
502
		AD.insert(AD.end(), sourceDeviceId.cbegin(), sourceDeviceId.cend());
johan's avatar
johan committed
503 504 505

		for(size_t i=0; i<recipients.size(); i++) {
			std::vector<uint8_t> recipientAD{AD}; // copy AD
Johan Pascal's avatar
Johan Pascal committed
506
			recipientAD.insert(recipientAD.end(), recipients[i].deviceId.cbegin(), recipients[i].deviceId.cend()); //insert recipient device id(gruu)
johan's avatar
johan committed
507

508
			if (payloadDirectEncryption) {
Johan Pascal's avatar
Johan Pascal committed
509
				recipients[i].DRSession->ratchetEncrypt(plaintext, std::move(recipientAD), recipients[i].DRmessage, payloadDirectEncryption);
510
			} else {
Johan Pascal's avatar
Johan Pascal committed
511
				recipients[i].DRSession->ratchetEncrypt(randomSeed, std::move(recipientAD), recipients[i].DRmessage, payloadDirectEncryption);
512
			}
johan's avatar
johan committed
513 514 515 516
		}
	}

	template <typename Curve>
Johan Pascal's avatar
Johan Pascal committed
517
	std::shared_ptr<DR<Curve>> decryptMessage(const std::string& sourceDeviceId, const std::string& recipientDeviceId, const std::string& recipientUserId, std::vector<std::shared_ptr<DR<Curve>>>& DRSessions, const std::vector<uint8_t>& DRmessage, const std::vector<uint8_t>& cipherMessage, std::vector<uint8_t>& plaintext) {
518
		bool payloadDirectEncryption = (cipherMessage.size() == 0); // if we do not have any cipher message, then we must be in payload direct encryption mode: the payload is in the DR message
519
		std::vector<uint8_t> AD; // the Associated Data authenticated by the AEAD scheme used in DR encrypt/decrypt
520 521 522 523 524 525 526 527 528 529 530 531 532

		/* Prepare the AD given to ratchet decrypt, is inpacted by message type
		 * - Payload in the cipherMessage: auth tag from cipherMessage || source Device Id || recipient Device Id
		 * - Payload in the DR message: recipient User Id || source Device Id || recipient Device Id
		 */
		if (!payloadDirectEncryption) { // payload in cipher message
			// check cipher Message validity, it must be at least auth tag bytes long
			if (cipherMessage.size()<lime::settings::DRMessageAuthTagSize) {
				throw BCTBX_EXCEPTION << "Invalid cipher message - too short";
			}
			AD.assign(cipherMessage.cend()-lime::settings::DRMessageAuthTagSize, cipherMessage.cend());
		} else { // payload in DR message
			AD.assign(recipientUserId.cbegin(), recipientUserId.cend());
johan's avatar
johan committed
533
		}
Johan Pascal's avatar
Johan Pascal committed
534 535
		AD.insert(AD.end(), sourceDeviceId.cbegin(), sourceDeviceId.cend());
		AD.insert(AD.end(), recipientDeviceId.cbegin(), recipientDeviceId.cend());
johan's avatar
johan committed
536

537
		// buffer to store the random seed used to derive key and IV to decrypt message
538
		lime::sBuffer<lime::settings::DRrandomSeedSize> randomSeed;
johan's avatar
johan committed
539 540

		for (auto& DRSession : DRSessions) {
541 542
			bool decryptStatus = false;
			try {
543
				// if payload is in the message, got the output directly in the plaintext buffer
544
				if (payloadDirectEncryption) {
Johan Pascal's avatar
Johan Pascal committed
545
					decryptStatus = DRSession->ratchetDecrypt(DRmessage, AD, plaintext, payloadDirectEncryption);
546
				} else {
Johan Pascal's avatar
Johan Pascal committed
547
					decryptStatus = DRSession->ratchetDecrypt(DRmessage, AD, randomSeed, payloadDirectEncryption);
548
				}
549
			} catch (BctbxException &e) { // any bctbx Exception is just considered as decryption failed (it shall occurs in case of maximum skipped keys reached or inconsistency ib the direct Encryption flag)
Johan Pascal's avatar
Johan Pascal committed
550
				LIME_LOGW<<"Double Ratchet session failed to decrypt message and raised an exception saying : "<<e.what();
551 552 553
				decryptStatus = false; // lets keep trying with other sessions if provided
			}

554 555 556 557
			if (decryptStatus == true) { // we got the DR message correctly deciphered
				if (payloadDirectEncryption) { // we're done, payload was in the DR message
					return DRSession;
				}
johan's avatar
johan committed
558
				// recompute the AD used for this encryption: source Device Id || recipient User Id
Johan Pascal's avatar
Johan Pascal committed
559 560
				std::vector<uint8_t> localAD{sourceDeviceId.cbegin(), sourceDeviceId.cend()};
				localAD.insert(localAD.end(), recipientUserId.cbegin(), recipientUserId.cend());
johan's avatar
johan committed
561

562
				// resize plaintext vector: same as cipher message - authentication tag length
johan's avatar
johan committed
563 564
				plaintext.resize(cipherMessage.size()-lime::settings::DRMessageAuthTagSize);

565
				// rebuild the random key and IV from given seed
566
				// use HKDF - RFC 5869 with empty salt
567
				std::vector<uint8_t> emptySalt;
568
				emptySalt.clear();
569
				lime::sBuffer<lime::settings::DRMessageKeySize+lime::settings::DRMessageIVSize> randomKey;
570
				HMAC_KDF<SHA512>(emptySalt.data(), emptySalt.size(), randomSeed.data(), randomSeed.size(), lime::settings::hkdf_randomSeed_info, randomKey.data(), randomKey.size());
571

johan's avatar
johan committed
572
				// use it to decipher message
573 574 575 576 577 578
				if (AEAD_decrypt<AES256GCM>(randomKey.data(), lime::settings::DRMessageKeySize, // random key buffer hold key<DRMessageKeySize bytes> || IV<DRMessageIVSize bytes>
						randomKey.data()+lime::settings::DRMessageKeySize, lime::settings::DRMessageIVSize,
						cipherMessage.data(), cipherMessage.size()-lime::settings::DRMessageAuthTagSize, // cipherMessage is Message || auth tag
						localAD.data(), localAD.size(),
						cipherMessage.data()+cipherMessage.size()-lime::settings::DRMessageAuthTagSize, lime::settings::DRMessageAuthTagSize, // tag is in the last 16 bytes of buffer
						plaintext.data())) {
johan's avatar
johan committed
579 580 581 582 583 584 585 586 587
					return DRSession;
				} else {
					throw BCTBX_EXCEPTION << "Message key correctly deciphered but then failed to decipher message itself";
				}
			}
		}
		return nullptr; // no session correctly deciphered
	}

johan's avatar
johan committed
588
	/* template instanciations for C25519 and C448 encryption/decryption functions */
johan's avatar
johan committed
589
#ifdef EC25519_ENABLED
Johan Pascal's avatar
Johan Pascal committed
590 591
	template void encryptMessage<C255>(std::vector<RecipientInfos<C255>>& recipients, const std::vector<uint8_t>& plaintext, const std::string& recipientUserId, const std::string& sourceDeviceId, std::vector<uint8_t>& cipherMessage, const lime::EncryptionPolicy encryptionPolicy);
	template std::shared_ptr<DR<C255>> decryptMessage<C255>(const std::string& sourceId, const std::string& recipientDeviceId, const std::string& recipientUserId, std::vector<std::shared_ptr<DR<C255>>>& DRSessions, const std::vector<uint8_t>& DRmessage, const std::vector<uint8_t>& cipherMessage, std::vector<uint8_t>& plaintext);
johan's avatar
johan committed
592 593
#endif
#ifdef EC448_ENABLED
Johan Pascal's avatar
Johan Pascal committed
594 595
	template void encryptMessage<C448>(std::vector<RecipientInfos<C448>>& recipients, const std::vector<uint8_t>& plaintext, const std::string& recipientUserId, const std::string& sourceDeviceId, std::vector<uint8_t>& cipherMessage, const lime::EncryptionPolicy encryptionPolicy);
	template std::shared_ptr<DR<C448>> decryptMessage<C448>(const std::string& sourceId, const std::string& recipientDeviceId, const std::string& recipientUserId, std::vector<std::shared_ptr<DR<C448>>>& DRSessions, const std::vector<uint8_t>& DRmessage, const std::vector<uint8_t>& cipherMessage, std::vector<uint8_t>& plaintext);
johan's avatar
johan committed
596 597
#endif
}