Commit e4d01f85 authored by johan's avatar johan

DR message holds a random seed only, save 16 byte per message

+ minor detail in X3DH SK computation input
parent f50cdf1b
......@@ -77,7 +77,7 @@ namespace lime {
* @Brief Key Derivation Function used in Symmetric key ratchet chain.
* Impleted according to DR spec section 5.2 using HMAC-SHA256 for CK derivation and 512 for MK and IV derivation
* MK = HMAC-SHA512(CK, hkdf_mk_info) // get 48 bytes of it: first 32 to be key and last 16 to be IV
* CK = HMAC-SHA256(CK, hkdf_ck_info)
* CK = HMAC-SHA512(CK, hkdf_ck_info)
* hkdf_ck_info and hldf_mk_info being a distincts constant strings
*
* @param[in/out] CK Input/output buffer used as key to compute MK and then next CK
......@@ -101,13 +101,13 @@ namespace lime {
* @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
* @param[out] plaintext the output message(may be a vector or an array of unsigned char)
* @param[out] plaintext the output message : a fixed size vector, encrypted message is the random seed used to generate the key to encrypt the real message
* this vector need resizing before calling actual decrypt
*
* @return false if authentication failed
*
*/
static bool decrypt(const lime::DRMKey &MK, const std::vector<uint8_t> &ciphertext, const size_t headerSize, std::vector<uint8_t> &AD, std::array<uint8_t,48> &plaintext) {
static bool decrypt(const lime::DRMKey &MK, const std::vector<uint8_t> &ciphertext, const size_t headerSize, std::vector<uint8_t> &AD, std::array<uint8_t, lime::settings::DRrandomSeedSize> &plaintext) {
return (bctbx_aes_gcm_decrypt_and_auth(MK.data(), lime::settings::DRMessageKeySize, // MK buffer hold key<DRMessageKeySize bytes>||IV<DRMessageIVSize bytes>
ciphertext.data()+headerSize, plaintext.size(), // cipher text starts after header, length is the one computed for plaintext
AD.data(), AD.size(),
......@@ -120,7 +120,7 @@ namespace lime {
* @brief Encrypt as described is spec section 3.1
*
* @param[in] MK A buffer holding key<32 bytes> || IV<16 bytes>
* @param[in] plaintext the output message(may be a vector or an array of unsigned char)
* @param[in] plaintext the input message, it is a fixed size vector as we always encrypt the random seed only
* @param[in] AD Associated data
* @param[in] headerSize Size of the header included in ciphertext
* @param[in/out] ciphertext buffer holding: header<size depends on DHKey type>, will append to it: ciphertext || auth tag<16 bytes>
......@@ -129,7 +129,7 @@ namespace lime {
* @return false if something goes wrong
*
*/
static bool encrypt(const lime::DRMKey &MK, const std::array<uint8_t,48> &plaintext, const size_t headerSize, std::vector<uint8_t> &AD, std::vector<uint8_t> &ciphertext) {
static bool encrypt(const lime::DRMKey &MK, const std::array<uint8_t,lime::settings::DRrandomSeedSize> &plaintext, const size_t headerSize, std::vector<uint8_t> &AD, std::vector<uint8_t> &ciphertext) {
return (bctbx_aes_gcm_encrypt_and_tag(MK.data(), lime::settings::DRMessageKeySize, // MK buffer also hold the IV
plaintext.data(), plaintext.size(),
AD.data(), AD.size(),
......@@ -305,12 +305,12 @@ namespace lime {
/**
* @brief Encrypt using the double-ratchet algorithm.
*
* @param[in] plaintext Shall actally be a 48 bytes buffer holding key+IV for a GCM encryption to the actual message
* @param[in] plaintext Shall actally be a 32 bytes buffer holding the seed used to generate key+IV for a GCM encryption to the actual message
* @param[in] AD Associated Data, this buffer shall hold: source GRUU<...> || recipient GRUU<...> || actual message AEAD auth tag
* @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
*/
template <typename Curve>
void DR<Curve>::ratchetEncrypt(const array<uint8_t, 48>& plaintext, std::vector<uint8_t> &&AD, std::vector<uint8_t> &ciphertext) {
void DR<Curve>::ratchetEncrypt(const array<uint8_t, lime::settings::DRrandomSeedSize>& plaintext, std::vector<uint8_t> &&AD, std::vector<uint8_t> &ciphertext) {
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;
......@@ -348,7 +348,7 @@ namespace lime {
*
*/
template <typename Curve>
bool DR<Curve>::ratchetDecrypt(const std::vector<uint8_t> &ciphertext,const std::vector<uint8_t> &AD, array<uint8_t,48> &plaintext) {
bool DR<Curve>::ratchetDecrypt(const std::vector<uint8_t> &ciphertext,const std::vector<uint8_t> &AD, array<uint8_t,lime::settings::DRrandomSeedSize> &plaintext) {
// parse header
double_ratchet_protocol::DRHeader<Curve> header{ciphertext};
if (!header.valid()) { // check it is valid otherwise just stop
......@@ -428,12 +428,19 @@ namespace lime {
template <typename Curve>
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) {
// First generate a key and IV, use it to encrypt the given message, Associated Data are : sourceDeviceId || recipientUserId
// generate the random key : 32 bytes of key, 16 bytes of IV
// generate the random seed
bctbx_rng_context_t *RNG = bctbx_rng_context_new();
std::array<uint8_t,lime::settings::DRMessageKeySize+lime::settings::DRMessageIVSize> randomKey; // use the same size than the one used internally by Double Ratchet
bctbx_rng_get(RNG, randomKey.data(), randomKey.size());
std::array<uint8_t,lime::settings::DRrandomSeedSize> randomSeed{}; // this seed is sent in DR message and used to derivate random key + IV to encrypt the actual message
bctbx_rng_get(RNG, randomSeed.data(), randomSeed.size());
bctbx_rng_context_free(RNG);
// expansion of randomSeed to 48 bytes: 32 bytes random key + 16 bytes nonce
// use the expansion round of HKDF - RFC 5869
std::array<uint8_t,lime::settings::DRMessageKeySize+lime::settings::DRMessageIVSize> randomKey{};
std::vector<uint8_t> expansionRoundInput{lime::settings::hkdf_randomSeed_info.begin(), lime::settings::hkdf_randomSeed_info.end()};
expansionRoundInput.push_back(0x01);
bctbx_hmacSha512(randomSeed.data(), randomSeed.size(), expansionRoundInput.data(), expansionRoundInput.size(), randomKey.size(), randomKey.data());
// 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);
......@@ -460,7 +467,7 @@ namespace lime {
std::vector<uint8_t> recipientAD{AD}; // copy AD
recipientAD.insert(recipientAD.end(), recipients[i].deviceId.begin(), recipients[i].deviceId.end()); //insert recipient device id(gruu)
recipients[i].DRSession->ratchetEncrypt(randomKey, std::move(recipientAD), recipients[i].cipherHeader);
recipients[i].DRSession->ratchetEncrypt(randomSeed, std::move(recipientAD), recipients[i].cipherHeader);
}
bctbx_clean(randomKey.data(), randomKey.size());
}
......@@ -472,13 +479,13 @@ namespace lime {
AD.insert(AD.end(), sourceDeviceId.begin(), sourceDeviceId.end());
AD.insert(AD.end(), recipientDeviceId.begin(), recipientDeviceId.end());
// buffer to store the random key used to encrypt message
std::array<uint8_t,lime::settings::DRMessageKeySize+lime::settings::DRMessageIVSize> randomKey; /* use the same size than the one used internally by Double Ratchet */
// buffer to store the random seed used to derive key and IV to decrypt message
std::array<uint8_t, lime::settings::DRrandomSeedSize> randomSeed{};
for (auto& DRSession : DRSessions) {
bool decryptStatus = false;
try {
decryptStatus = DRSession->ratchetDecrypt(cipherHeader, AD, randomKey);
decryptStatus = DRSession->ratchetDecrypt(cipherHeader, AD, randomSeed);
} catch (BctbxException &e) { // any bctbx Exception is just considered as decryption failed (it shall occurs only in case of maximum skipped keys reached)
BCTBX_SLOGW<<"Double Ratchet session failed to decrypt message and raised an exception saying : "<<e.what();
decryptStatus = false; // lets keep trying with other sessions if provided
......@@ -492,6 +499,13 @@ namespace lime {
// resize plaintext vector as it is adressed directly by C library: same as cipher message - authentication tag length
plaintext.resize(cipherMessage.size()-lime::settings::DRMessageAuthTagSize);
// rebuild the random key and IV from given seed
// use the expansion round of HKDF - RFC 5869
std::array<uint8_t,lime::settings::DRMessageKeySize+lime::settings::DRMessageIVSize> randomKey{};
std::vector<uint8_t> expansionRoundInput{lime::settings::hkdf_randomSeed_info.begin(), lime::settings::hkdf_randomSeed_info.end()};
expansionRoundInput.push_back(0x01);
bctbx_hmacSha512(randomSeed.data(), randomSeed.size(), expansionRoundInput.data(), expansionRoundInput.size(), randomKey.size(), randomKey.data());
// use it to decipher message
if (bctbx_aes_gcm_decrypt_and_auth(randomKey.data(), lime::settings::DRMessageKeySize, // random key buffer hold key<DRMessageKeySize bytes> || IV<DRMessageIVSize bytes>
cipherMessage.data(), cipherMessage.size()-lime::settings::DRMessageAuthTagSize, // cipherMessage is Message || auth tag
......
......@@ -99,8 +99,8 @@ namespace lime {
DR<Curve> &operator=(DR<Curve> &a) = delete; // can't copy a session
~DR();
void ratchetEncrypt(const std::array<uint8_t, 48> &plaintext, std::vector<uint8_t> &&AD, std::vector<uint8_t> &ciphertext);
bool ratchetDecrypt(const std::vector<uint8_t> &cipherText, const std::vector<uint8_t> &AD, std::array<uint8_t, 48> &plaintext);
void ratchetEncrypt(const std::array<uint8_t, lime::settings::DRrandomSeedSize> &plaintext, std::vector<uint8_t> &&AD, std::vector<uint8_t> &ciphertext);
bool ratchetDecrypt(const std::vector<uint8_t> &cipherText, const std::vector<uint8_t> &AD, std::array<uint8_t, lime::settings::DRrandomSeedSize> &plaintext);
long int dbSessionId(void) const {return m_dbSessionId;}; // retrieve the session's local storage id
bool isActive(void) const {return m_active_status;} // return the current status of session
};
......
......@@ -38,7 +38,7 @@ namespace lime {
*
* Version 0x01:
* DRHeader is: Protocol Version Number<1 byte> || Packet Type <1 byte> || curveId <1 byte> || [X3DH Init message <variable>] || Ns<2 bytes> || PN<2 bytes> || DHs<...>
* Message is : DRheaer<...> || cipherMessageKeyK<48 bytes> || Key auth tag<16 bytes> || cipherText<...> || Message auth tag<16 bytes>
* Message is : DRheader<...> || cipherMessageKeyK<32 bytes> || Key auth tag<16 bytes> || cipherText<...> || Message auth tag<16 bytes>
*
* Associated Data are transmitted separately: ADk for the Key auth tag, and ADm for the Message auth tag
* Message AEAD on : (ADm, message plain text) keyed by message Key(include IV)
......
......@@ -38,10 +38,15 @@ namespace settings {
// Sending, Receiving and Root key chain use 32 bytes keys (spec 3.2)
constexpr size_t DRChainKeySize=32;
// Message Key are composed of a 32 bytes key and 16 bytes of IV
// DR Message Key are composed of a 32 bytes key and 16 bytes of IV
constexpr size_t DRMessageKeySize=32;
constexpr size_t DRMessageIVSize=16;
// Message Key is based on a message seed(sent in the DR message)
// Message key and nonce are derived from this seed and have the same length as DR Message Key
constexpr size_t DRrandomSeedSize=32;
const std::string hkdf_randomSeed_info{"DR Message Key Derivation"};
// AEAD generates tag 16 bytes long
constexpr size_t DRMessageAuthTagSize=16;
......@@ -72,7 +77,7 @@ namespace settings {
/* */
/******************************************************************************/
const std::string X3DH_SK_info{"Lime"}; // shall be an ASCII string identifying the application (X3DH spec section 2.1)
const std::string X3DH_AD_info{"X3DH Authenticated Data"}; // used to generate a shared AD based on Ik and deviceID
const std::string X3DH_AD_info{"X3DH Associated Data"}; // used to generate a shared AD based on Ik and deviceID
}
}
......
......@@ -41,7 +41,7 @@ namespace lime {
* PRK = HMAC-SHA512(salt, input)
* Output = HMAC-SHA512(PRK, info || 0x01)
*
* with salt being a 0 filled buffer of SHA256 output length(32 bytes)
* with salt being a 0 filled buffer of SHA512 output length(64 bytes) X3DH spec section 2.2 KDF
*
* @param[in] input Input buffer holding F || DH1 || DH2 || DH3 [|| DH4] or Ik initiator || Ik receiver || Initiator device Id || Receiver device Id
* @param[in] info The string used as info
......@@ -53,7 +53,7 @@ namespace lime {
// expansion round input shall be info || 0x01
std::vector<uint8_t> expansionRoundInput{info.begin(), info.end()};
expansionRoundInput.push_back(0x01);
std::array<uint8_t,32> zeroFilledSalt; zeroFilledSalt.fill(0);
std::array<uint8_t,64> zeroFilledSalt; zeroFilledSalt.fill(0);
bctbx_hmacSha512(zeroFilledSalt.data(), zeroFilledSalt.size(), input.data(), input.size(), prk.size(), prk.data());
bctbx_hmacSha512(prk.data(), prk.size(), expansionRoundInput.data(), expansionRoundInput.size(), output.size(), output.data());
bctbx_clean(prk.data(), prk.size());
......@@ -102,7 +102,8 @@ namespace lime {
bctbx_DestroyEDDSAContext(EDDSAContext); // don't need the EDDSA anymore, all ECDH from now
// Initiate HKDF input : We will compute HKDF with a concat of F and all DH computed, see X3DH spec section 2.2 for what is F
std::vector<uint8_t> HKDF_input(X<Curve>::keyLength(), 0xFF);
std::vector<uint8_t> HKDF_input(ED<Curve>::keyLength(), 0xFF);
HKDF_input.reserve(ED<Curve>::keyLength() + X<Curve>::keyLength()*4); // reserve memory for DH4 anyway, each DH has the same size the key has
// Compute DH1 = DH(self Ik, peer SPk) - selfIk context already holds selfIk.
bctbx_ECDHSetPeerPublicKey(selfIk, peerBundle.SPk.data(), peerBundle.SPk.size());
......@@ -183,8 +184,8 @@ namespace lime {
// DH4 = DH(OPk, Ek) if peer used an OPk
// Initiate HKDF input : We will compute HKDF with a concat of F and all DH computed, see X3DH spec section 2.2 for what is F: keyLength bytes set to 0xFF
std::vector<uint8_t> HKDF_input(X<Curve>::keyLength(), 0xFF);
HKDF_input.reserve(X<Curve>::keyLength()*5); // reserve memory for DH4 anyway, each DH has the same size the key has
std::vector<uint8_t> HKDF_input(ED<Curve>::keyLength(), 0xFF);
HKDF_input.reserve(ED<Curve>::keyLength() + X<Curve>::keyLength()*4); // reserve memory for DH4 anyway, each DH has the same size the key has
// DH1 first
// Convert peer Ik ED keys to X keys:: TODO what if peer directly send his X key instead of ED one as he got it in X form anyway?
......
......@@ -262,25 +262,25 @@ bool DR_message_holdsX3DHInit(std::vector<uint8_t> &message, bool &haveOPk) {
if (message[1] !=static_cast<uint8_t>(lime::double_ratchet_protocol::DR_message_type::x3dhinit)) return false;
// check packet length, packet is :
// header<3 bytes>, X3DH init packet, Ns+PN<4 bytes>, DHs<X<Curve>::keyLength>, Cipher message Key+tag: DRMessageKey + DRMessageIV <48 bytes>, key auth tag<16 bytes> = <71 + X<Curve>::keyLengh + X3DH init size>
// header<3 bytes>, X3DH init packet, Ns+PN<4 bytes>, DHs<X<Curve>::keyLength>, Cipher message RandomSeed<32 bytes>, key auth tag<16 bytes> = <55 + X<Curve>::keyLengh + X3DH init size>
// X3DH init size = OPk_flag<1 byte> + selfIK<ED<Curve>::keyLength> + EK<X<Curve>::keyLenght> + SPk id<4 bytes> + OPk id (if flag is 1)<4 bytes>
switch (message[2]) {
case static_cast<uint8_t>(lime::CurveId::c25519):
if (message[3] == 0x00) { // no OPk in the X3DH init message
if (message.size() != (71 + X<C255>::keyLength() + 5 + ED<C255>::keyLength() + X<C255>::keyLength())) return false;
if (message.size() != (55 + X<C255>::keyLength() + 5 + ED<C255>::keyLength() + X<C255>::keyLength())) return false;
haveOPk=false;
} else { // OPk present in the X3DH init message
if (message.size() != (71 + X<C255>::keyLength() + 9 + ED<C255>::keyLength() + X<C255>::keyLength())) return false;
if (message.size() != (55 + X<C255>::keyLength() + 9 + ED<C255>::keyLength() + X<C255>::keyLength())) return false;
haveOPk=true;
}
return true;
break;
case static_cast<uint8_t>(lime::CurveId::c448):
if (message[3] == 0x00) { // no OPk in the X3DH init message
if (message.size() != (71 + X<C448>::keyLength() + 5 + ED<C448>::keyLength() + X<C448>::keyLength())) return false;
if (message.size() != (55 + X<C448>::keyLength() + 5 + ED<C448>::keyLength() + X<C448>::keyLength())) return false;
haveOPk=false;
} else { // OPk present in the X3DH init message
if (message.size() != (71 + X<C448>::keyLength() + 9 + ED<C448>::keyLength() + X<C448>::keyLength())) return false;
if (message.size() != (55 + X<C448>::keyLength() + 9 + ED<C448>::keyLength() + X<C448>::keyLength())) return false;
haveOPk=true;
}
return true;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment