lime_impl.hpp 11.6 KB
Newer Older
johan's avatar
johan committed
1 2
/*
	lime_impl.hpp
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 20 21 22 23 24 25 26 27 28

	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/>.
*/
#ifndef lime_impl_hpp
#define lime_impl_hpp

#include <memory>
#include <vector>
#include <unordered_map>
#include <queue>

#include "lime/lime.hpp"
#include "lime_lime.hpp"
29
#include "lime_crypto_primitives.hpp"
johan's avatar
johan committed
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
#include "lime_localStorage.hpp"
#include "lime_double_ratchet.hpp"
#include "lime_x3dh_protocol.hpp"

namespace lime {
	// an enum used by network state engine to manage sequence packet sending(at user creation)
	enum class network_state : uint8_t {done=0x00, sendSPk=0x01, sendOPk=0x02};

	template <typename Curve>
	struct callbackUserData;

	/* templated declaration of Lime can be specialised using C255 or C448 according to the elliptic curve we want to use */
	template <typename Curve>
	class Lime : public LimeGeneric, public std::enable_shared_from_this<Lime<Curve>> {
		private:
			/*** data members ***/
			/* general purpose */
47
			std::shared_ptr<RNG> m_RNG; // Random Number Generator context
johan's avatar
johan committed
48 49 50
			std::string m_selfDeviceId; // self device Id, shall be the GRUU

			/* X3DH keys */
51
			DSApair<Curve> m_Ik; // our identity key pair, is loaded from DB only if requested(to sign a SPK or to perform X3DH init)
johan's avatar
johan committed
52 53 54 55 56 57 58
			bool m_Ik_loaded; // did we load the Ik yet?

			/* local storage related */
			std::shared_ptr<lime::Db> m_localStorage; // shared pointer would be used/stored in Double Ratchet Sessions
			long int m_db_Uid; // the Uid in database, retrieved at creation/load, used for faster access

			/* network related */
59
			limeX3DHServerPostData m_X3DH_post_data; // externally provided function to communicate with x3dh server
johan's avatar
johan committed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
			std::string m_X3DH_Server_URL; // url of x3dh key server

			/* Double ratchet related */
			std::unordered_map<std::string, std::shared_ptr<DR<Curve>>> m_DR_sessions_cache; // store already loaded DR session

			/* encryption queue: encryption requesting asynchronous operation(connection to X3DH server) are queued to avoid repeating a request to server */
			std::shared_ptr<callbackUserData<Curve>> m_ongoing_encryption;
			std::queue<std::shared_ptr<callbackUserData<Curve>>> m_encryption_queue;

			/*** Private functions ***/
			/* database related functions, implementation is in lime_localStorage.cpp */
			// create user in DB, throw an exception if already there or something went wrong
			bool create_user();
			// user load from DB is implemented directly as a Db member function, output of it is passed to Lime<> ctor
			void get_SelfIdentityKey(); // check our Identity key pair is loaded in Lime object, retrieve it from DB if it isn't
			void cache_DR_sessions(std::vector<recipientInfos<Curve>> &internal_recipients, std::vector<std::string> &missing_devices); // loop on internal recipient an try to load in DR session cache the one which have no session attached 
			void get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDRSessionId, std::vector<std::shared_ptr<DR<Curve>>> &DRSessions); // load from local storage in DRSessions all DR session matching the peerDeviceId, ignore the one picked by id in 2nd arg
77
			long int store_peerDevice(const std::string &peerDeviceId, const DSA<Curve, lime::DSAtype::publicKey> &Ik); // store given peer Device Id and public identity key, return the Id used in table to store it
johan's avatar
johan committed
78 79

			/* X3DH related  - part related to exchange with server or localStorage - implemented in lime_x3dh_protocol.cpp or lime_localStorage.cpp */
80 81 82
			void X3DH_generate_SPk(X<Curve, lime::Xtype::publicKey> &publicSPk, DSA<Curve, lime::DSAtype::signature> &SPk_sig, uint32_t &SPk_id); // generate a new Signed Pre-Key key pair, store it in DB and set its public key, signature and Id in given params
			void X3DH_generate_OPks(std::vector<X<Curve, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number); // generate a new batch of OPks, store them in base and fill the vector with information to be sent to X3DH server
			void X3DH_get_SPk(uint32_t SPk_id, Xpair<Curve> &SPk); // retrieve matching SPk from localStorage, throw an exception if not found
johan's avatar
johan committed
83
			bool is_currentSPk_valid(void); // check validity of current SPk
84
			void X3DH_get_OPk(uint32_t OPk_id, Xpair<Curve> &OPk); // retrieve matching OPk from localStorage, throw an exception if not found
85
			void X3DH_updateOPkStatus(const std::vector<uint32_t> &OPkIds); // update OPks to tag those not anymore on X3DH server but not used and destroyed yet
johan's avatar
johan committed
86 87 88 89 90
			/* X3DH related  - part related to X3DH DR session initiation, implemented in lime_x3dh.cpp */
			void X3DH_init_sender_session(const std::vector<X3DH_peerBundle<Curve>> &peersBundle); // compute a sender X3DH using the data from peer bundle, then create and load the DR_Session
			std::shared_ptr<DR<Curve>> X3DH_init_receiver_session(const std::vector<uint8_t> X3DH_initMessage, const std::string &senderDeviceId); // from received X3DH init packet, try to compute the shared secrets, then create the DR_Session

			/* network related */
91 92 93
			void postToX3DHServer(std::shared_ptr<callbackUserData<Curve>> userData, const std::vector<uint8_t> &message); // send a request to X3DH server
			void process_response(std::shared_ptr<callbackUserData<Curve>> userData, int responseCode, const std::vector<uint8_t> &responseBody) noexcept; // callback on server response
			void cleanUserData(std::shared_ptr<callbackUserData<Curve>> userData); // clean user data
johan's avatar
johan committed
94 95 96 97 98 99 100 101

		public: /* Implement API defined in lime.hpp in factory abstract class */
			/**
			 * @brief Constructors:
			  * - one would create a new user in localStorage and assign it a server and curve id
			  * - one to load the user from db based on provided user Id(which shall be GRUU)
			  * Note: ownership of localStorage pointer is transfered to a shared pointer, private menber of Lime class
			 */
102 103
			Lime(std::unique_ptr<lime::Db> &&localStorage, const std::string &deviceId, const std::string &url, const limeX3DHServerPostData &X3DH_post_data);
			Lime(std::unique_ptr<lime::Db> &&localStorage, const std::string &deviceId, const std::string &url, const limeX3DHServerPostData &X3DH_post_data, const long Uid);
johan's avatar
johan committed
104 105 106 107
			~Lime();
			Lime(Lime<Curve> &a) = delete; // can't copy a session, force usage of shared pointers
			Lime<Curve> &operator=(Lime<Curve> &a) = delete; // can't copy a session

johan's avatar
johan committed
108
			void publish_user(const limeCallback &callback, const uint16_t OPkInitialBatchSize) override;
johan's avatar
johan committed
109
			void delete_user(const limeCallback &callback) override;
johan's avatar
johan committed
110

johan's avatar
johan committed
111 112 113 114 115 116 117
			/**
			 * @brief Check if the current SPk needs to be updated, if yes, generate a new one and publish it on server
			 *
			 * @param[in] callback 	Called with success or failure when operation is completed.
			*/
			void update_SPk(const limeCallback &callback) override;

johan's avatar
johan committed
118 119 120 121 122 123 124 125 126 127 128
			/**
			 * @brief check if we shall upload more OPks on X3DH server
			 * - ask server four our keys (returns the count and all their Ids)
			 * - check if it's under the low limit, if yes, generate a batch of keys and upload them
			 *
			 * @param[in]	callback 		Called with success or failure when operation is completed.
			 * @param[in]	OPkServerLowLimit	If server holds less OPk than this limit, generate and upload a batch of OPks
			 * @param[in]	OPkBatchSize		Number of OPks in a batch uploaded to server
			*/
			void update_OPk(const limeCallback &callback, uint16_t OPkServerLowLimit, uint16_t OPkBatchSize) override;

129 130 131 132 133 134
			/**
			 * @brief Retrieve self public Identity key
			 *
			 * @param[out]	Ik	the public EdDSA formatted Identity key
			 */
			virtual void get_Ik(std::vector<uint8_t> &Ik) override;
johan's avatar
johan committed
135

johan's avatar
johan committed
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
			void encrypt(std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients, std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage, const limeCallback &callback) override;
			bool decrypt(const std::string &recipientUserId, const std::string &senderDeviceId, const std::vector<uint8_t> &cipherHeader, const std::vector<uint8_t> &cipherMessage, std::vector<uint8_t> &plainMessage) override;
	};

	// structure holding user data during callback
	template <typename Curve>
	struct callbackUserData {
		// always needed
		std::weak_ptr<Lime<Curve>> limeObj; // Lime is owned by the LimeManager, it shall no be destructed, do not own this with a shared_ptr as Lime obj may own the callbackUserData obj thus creating circular reference
		const limeCallback callback; // is a lambda closure, not real idea of what is its lifetime but it seems ok to hold it this way
		// needed for encryption: get a shared ref to keep params alive
		std::shared_ptr<const std::string> recipientUserId;
		std::shared_ptr<std::vector<recipientData>> recipients;
		std::shared_ptr<const std::vector<uint8_t>> plainMessage;
		std::shared_ptr<std::vector<uint8_t>> cipherMessage;
		lime::network_state network_state_machine; /* used to run a simple state machine at user creation to perform sequence of packet sending: registerUser, postSPk, postOPks */
johan's avatar
johan committed
152 153 154 155 156 157 158 159
		uint16_t OPkServerLowLimit; /* Used when fetching from server self OPk to check if we shall upload more */
		uint16_t OPkBatchSize;

		// created at user create/delete and keys Post
		callbackUserData(std::weak_ptr<Lime<Curve>> thiz, const limeCallback &callbackRef, uint16_t OPkInitialBatchSize=lime::settings::OPk_initialBatchSize, bool startRegisterUserSequence=false)
			: limeObj{thiz}, callback{callbackRef},
			recipientUserId{nullptr}, recipients{nullptr}, plainMessage{nullptr}, cipherMessage{nullptr}, network_state_machine{startRegisterUserSequence?lime::network_state::sendSPk:lime::network_state::done},
			OPkServerLowLimit(0), OPkBatchSize(OPkInitialBatchSize) {};
johan's avatar
johan committed
160

johan's avatar
johan committed
161 162
		// created at update: getSelfOPks
		callbackUserData(std::weak_ptr<Lime<Curve>> thiz, const limeCallback &callbackRef, uint16_t OPkServerLowLimit, uint16_t OPkBatchSize)
johan's avatar
johan committed
163
			: limeObj{thiz}, callback{callbackRef},
johan's avatar
johan committed
164 165 166
			recipientUserId{nullptr}, recipients{nullptr}, plainMessage{nullptr}, cipherMessage{nullptr}, network_state_machine{lime::network_state::done},
			OPkServerLowLimit{OPkServerLowLimit}, OPkBatchSize{OPkBatchSize} {};
		// created at encrypt(getPeerBundle)
johan's avatar
johan committed
167 168 169 170
		callbackUserData(std::weak_ptr<Lime<Curve>> thiz, const limeCallback &callbackRef,
				std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients,
				std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage)
			: limeObj{thiz}, callback{callbackRef},
johan's avatar
johan committed
171 172
			recipientUserId{recipientUserId}, recipients{recipients}, plainMessage{plainMessage}, cipherMessage{cipherMessage}, network_state_machine {lime::network_state::done}, // copy construct all shared_ptr
			OPkServerLowLimit(0), OPkBatchSize(0) {};
johan's avatar
johan committed
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
		// do not copy callback data, force passing the pointer around after creation
		callbackUserData(callbackUserData &a) = delete;
		callbackUserData operator=(callbackUserData &a) = delete;
	};


/* this template is intanciated in lime.cpp, do not re-instanciate it anywhere else */
#ifdef EC25519_ENABLED
	extern template class Lime<C255>;
#endif

#ifdef EC448_ENABLED
	extern template class Lime<C448>;
#endif

}
#endif /* lime_impl_hpp */