Skip to content
Snippets Groups Projects
Commit f719236b authored by Nicolas Werner's avatar Nicolas Werner
Browse files

Implement file encryption and decryption

No idea, if that is secure yet.
parent 989ac732
No related branches found
No related tags found
No related merge requests found
......@@ -55,19 +55,6 @@ private:
std::string msg_;
};
class sodium_exception : public std::exception
{
public:
sodium_exception(std::string func, const char *msg)
: msg_(func + ": " + std::string(msg))
{}
virtual const char *what() const throw() { return msg_.c_str(); }
private:
std::string msg_;
};
template<class T>
std::string
pickle(typename T::olm_type *object, const std::string &key)
......@@ -244,12 +231,6 @@ encrypt_exported_sessions(const mtx::crypto::ExportedSessionKeys &keys, std::str
mtx::crypto::ExportedSessionKeys
decrypt_exported_sessions(const std::string &data, std::string pass);
std::string
base642bin(const std::string &b64);
std::string
bin2base64(const std::string &b64);
BinaryBuf
derive_key(const std::string &pass, const BinaryBuf &salt);
......
#pragma once
#include <algorithm>
#include <string>
#include <vector>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <sodium.h>
#include <boost/algorithm/string.hpp>
#include "mtx/common.hpp"
namespace mtx {
namespace crypto {
class sodium_exception : public std::exception
{
public:
sodium_exception(std::string func, const char *msg)
: msg_(func + ": " + std::string(msg))
{}
virtual const char *what() const throw() { return msg_.c_str(); }
private:
std::string msg_;
};
//! Data representation used to interact with libolm.
using BinaryBuf = std::vector<uint8_t>;
......@@ -32,6 +40,19 @@ create_buffer(std::size_t nbytes)
return buf;
}
inline BinaryBuf
to_binary_buf(const std::string &str)
{
return BinaryBuf(reinterpret_cast<const uint8_t *>(str.data()),
reinterpret_cast<const uint8_t *>(str.data()) + str.size());
}
inline std::string
to_string(const BinaryBuf &buf)
{
return std::string(reinterpret_cast<const char *>(buf.data()), buf.size());
}
//! Simple wrapper around the OpenSSL PKCS5_PBKDF2_HMAC function
BinaryBuf
PBKDF2_HMAC_SHA_512(const std::string pass, const BinaryBuf salt, uint32_t iterations);
......@@ -45,6 +66,18 @@ AES_CTR_256_Decrypt(const std::string ciphertext, const BinaryBuf aes256Key, Bin
BinaryBuf
HMAC_SHA256(const BinaryBuf hmacKey, const BinaryBuf data);
std::string
sha256(const std::string &data);
//! Decrypt matrix EncryptedFile
BinaryBuf
decrypt_file(const std::string &ciphertext, const mtx::crypto::EncryptedFile &encryption_info);
//! Encrypt matrix EncryptedFile
// Remember to set the url member of the EncryptedFile struct!
std::pair<BinaryBuf, mtx::crypto::EncryptedFile>
encrypt_file(const std::string &plaintext);
//! Translates the data back into the binary buffer, taking care
//! to remove the header and footer elements.
std::string
......@@ -63,5 +96,23 @@ uint32_to_uint8(uint8_t b[4], uint32_t u32);
void
print_binary_buf(const BinaryBuf buf);
std::string
base642bin(const std::string &b64);
std::string
bin2base64(const std::string &bin);
std::string
base642bin_unpadded(const std::string &b64);
std::string
bin2base64_unpadded(const std::string &bin);
std::string
base642bin_urlsafe_unpadded(const std::string &b64);
std::string
bin2base64_urlsafe_unpadded(const std::string &bin);
} // namespace crypto
} // namespace mtx
\ No newline at end of file
} // namespace mtx
#include <iostream>
#include <openssl/aes.h>
#include <openssl/sha.h>
#include "mtxclient/crypto/client.hpp"
#include "mtxclient/crypto/types.hpp"
#include "mtxclient/crypto/utils.hpp"
......@@ -669,50 +672,6 @@ mtx::crypto::decrypt_exported_sessions(const std::string &data, std::string pass
return json::parse(plaintext);
}
std::string
mtx::crypto::base642bin(const std::string &b64)
{
std::size_t bin_maxlen = b64.size();
std::size_t bin_len;
const char *max_end;
auto ciphertext = create_buffer(bin_maxlen);
const int rc = sodium_base642bin(reinterpret_cast<unsigned char *>(ciphertext.data()),
ciphertext.size(),
b64.data(),
b64.size(),
nullptr,
&bin_len,
&max_end,
sodium_base64_VARIANT_ORIGINAL);
if (rc != 0)
throw sodium_exception{"sodium_base642bin", "encoding failed"};
if (bin_len != bin_maxlen)
ciphertext.resize(bin_len);
return std::string(std::make_move_iterator(ciphertext.begin()),
std::make_move_iterator(ciphertext.end()));
}
std::string
mtx::crypto::bin2base64(const std::string &bin)
{
auto base64buf =
create_buffer(sodium_base64_encoded_len(bin.size(), sodium_base64_VARIANT_ORIGINAL));
sodium_bin2base64(reinterpret_cast<char *>(base64buf.data()),
base64buf.size(),
reinterpret_cast<const unsigned char *>(bin.data()),
bin.size(),
sodium_base64_VARIANT_ORIGINAL);
// Removing the null byte.
return std::string(base64buf.begin(), base64buf.end() - 1);
}
BinaryBuf
mtx::crypto::derive_key(const std::string &pass, const BinaryBuf &salt)
{
......
#include "mtxclient/crypto/utils.hpp"
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <algorithm>
#include <iomanip>
#include <iostream>
namespace mtx {
......@@ -33,7 +40,7 @@ AES_CTR_256_Encrypt(const std::string plaintext, const BinaryBuf aes256Key, Bina
int ciphertext_len;
// The ciphertext expand up to block size, which is 128 for AES256
BinaryBuf encrypted = create_buffer(plaintext.size() + 128);
BinaryBuf encrypted = create_buffer(plaintext.size() + AES_BLOCK_SIZE);
uint8_t *iv_data = iv.data();
// need to set bit 63 to 0
......@@ -128,6 +135,90 @@ AES_CTR_256_Decrypt(const std::string ciphertext, const BinaryBuf aes256Key, Bin
return decrypted;
}
std::string
sha256(const std::string &data)
{
bool success = false;
std::string hashed;
EVP_MD_CTX *context = EVP_MD_CTX_new();
if (context != NULL) {
if (EVP_DigestInit_ex(context, EVP_sha256(), NULL)) {
if (EVP_DigestUpdate(context, data.c_str(), data.length())) {
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int lengthOfHash = 0;
if (EVP_DigestFinal_ex(context, hash, &lengthOfHash)) {
hashed = std::string(hash, hash + lengthOfHash);
success = true;
}
}
}
EVP_MD_CTX_free(context);
}
if (success)
return hashed;
throw std::runtime_error("sha256 failed!");
}
BinaryBuf
decrypt_file(const std::string &ciphertext, const mtx::crypto::EncryptedFile &encryption_info)
{
if (encryption_info.v != "v2")
throw std::invalid_argument("Unsupported encrypted file version");
if (encryption_info.key.kty != "oct")
throw std::invalid_argument("Unsupported key type");
if (encryption_info.key.alg != "A256CTR")
throw std::invalid_argument("Unsupported algorithm");
// Be careful, the key should be urlsafe and unpadded, the iv and sha only need to
// be unpadded
if (bin2base64_unpadded(sha256(ciphertext)) != encryption_info.hashes.at("sha256"))
throw std::invalid_argument(
"sha256 of encrypted file does not match the ciphertext, expected '" +
bin2base64_unpadded(sha256(ciphertext)) + "', got '" +
encryption_info.hashes.at("sha256") + "'");
return AES_CTR_256_Decrypt(
ciphertext,
to_binary_buf(base642bin_urlsafe_unpadded(encryption_info.key.k)),
to_binary_buf(base642bin_unpadded(encryption_info.iv)));
}
std::pair<BinaryBuf, mtx::crypto::EncryptedFile>
encrypt_file(const std::string &plaintext)
{
mtx::crypto::EncryptedFile encryption_info;
// not sure if 16 bytes would be enough, 32 seems to be safe though
BinaryBuf key = create_buffer(32);
BinaryBuf iv = create_buffer(32);
BinaryBuf cyphertext = AES_CTR_256_Encrypt(plaintext, key, iv);
// Be careful, the key should be urlsafe and unpadded, the iv and sha only need to
// be unpadded
JWK web_key;
web_key.ext = true;
web_key.kty = "oct";
web_key.key_ops = {"encrypt", "decrypt"};
web_key.alg = "A256CTR";
web_key.k = bin2base64_urlsafe_unpadded(to_string(key));
web_key.ext = true;
encryption_info.key = web_key;
encryption_info.iv = bin2base64_unpadded(to_string(iv));
encryption_info.hashes["sha256"] = bin2base64_unpadded(sha256(to_string(cyphertext)));
encryption_info.v = "v2";
return std::make_pair(cyphertext, encryption_info);
}
template<typename T>
void
remove_substrs(std::basic_string<T> &s, const std::basic_string<T> &p)
......@@ -185,5 +276,135 @@ uint32_to_uint8(uint8_t b[4], uint32_t u32)
b[0] = (uint8_t)(u32 >>= 8);
}
std::string
base642bin(const std::string &b64)
{
std::size_t bin_maxlen = b64.size();
std::size_t bin_len;
const char *max_end;
auto ciphertext = create_buffer(bin_maxlen);
const int rc = sodium_base642bin(reinterpret_cast<unsigned char *>(ciphertext.data()),
ciphertext.size(),
b64.data(),
b64.size(),
nullptr,
&bin_len,
&max_end,
sodium_base64_VARIANT_ORIGINAL);
if (rc != 0)
throw sodium_exception{"sodium_base642bin", "encoding failed"};
if (bin_len != bin_maxlen)
ciphertext.resize(bin_len);
return std::string(std::make_move_iterator(ciphertext.begin()),
std::make_move_iterator(ciphertext.end()));
}
std::string
bin2base64(const std::string &bin)
{
auto base64buf =
create_buffer(sodium_base64_encoded_len(bin.size(), sodium_base64_VARIANT_ORIGINAL));
sodium_bin2base64(reinterpret_cast<char *>(base64buf.data()),
base64buf.size(),
reinterpret_cast<const unsigned char *>(bin.data()),
bin.size(),
sodium_base64_VARIANT_ORIGINAL);
// Removing the null byte.
return std::string(base64buf.begin(), base64buf.end() - 1);
}
std::string
base642bin_unpadded(const std::string &b64)
{
std::size_t bin_maxlen = b64.size();
std::size_t bin_len;
const char *max_end;
auto ciphertext = create_buffer(bin_maxlen);
const int rc = sodium_base642bin(reinterpret_cast<unsigned char *>(ciphertext.data()),
ciphertext.size(),
b64.data(),
b64.size(),
nullptr,
&bin_len,
&max_end,
sodium_base64_VARIANT_ORIGINAL_NO_PADDING);
if (rc != 0)
throw sodium_exception{"sodium_base642bin", "encoding failed"};
if (bin_len != bin_maxlen)
ciphertext.resize(bin_len);
return std::string(std::make_move_iterator(ciphertext.begin()),
std::make_move_iterator(ciphertext.end()));
}
std::string
bin2base64_unpadded(const std::string &bin)
{
auto base64buf = create_buffer(
sodium_base64_encoded_len(bin.size(), sodium_base64_VARIANT_ORIGINAL_NO_PADDING));
sodium_bin2base64(reinterpret_cast<char *>(base64buf.data()),
base64buf.size(),
reinterpret_cast<const unsigned char *>(bin.data()),
bin.size(),
sodium_base64_VARIANT_ORIGINAL_NO_PADDING);
// Removing the null byte.
return std::string(base64buf.begin(), base64buf.end() - 1);
}
std::string
base642bin_urlsafe_unpadded(const std::string &b64)
{
std::size_t bin_maxlen = b64.size();
std::size_t bin_len;
const char *max_end;
auto ciphertext = create_buffer(bin_maxlen);
const int rc = sodium_base642bin(reinterpret_cast<unsigned char *>(ciphertext.data()),
ciphertext.size(),
b64.data(),
b64.size(),
nullptr,
&bin_len,
&max_end,
sodium_base64_VARIANT_URLSAFE_NO_PADDING);
if (rc != 0)
throw sodium_exception{"sodium_base642bin", "encoding failed"};
if (bin_len != bin_maxlen)
ciphertext.resize(bin_len);
return std::string(std::make_move_iterator(ciphertext.begin()),
std::make_move_iterator(ciphertext.end()));
}
std::string
bin2base64_urlsafe_unpadded(const std::string &bin)
{
auto base64buf = create_buffer(
sodium_base64_encoded_len(bin.size(), sodium_base64_VARIANT_URLSAFE_NO_PADDING));
sodium_bin2base64(reinterpret_cast<char *>(base64buf.data()),
base64buf.size(),
reinterpret_cast<const unsigned char *>(bin.data()),
bin.size(),
sodium_base64_VARIANT_URLSAFE_NO_PADDING);
// Removing the null byte.
return std::string(base64buf.begin(), base64buf.end() - 1);
}
} // namespace crypto
} // namespace mtx
......@@ -24,6 +24,8 @@ using namespace mtx::responses;
using namespace std;
using namespace nlohmann;
struct OlmCipherContent
{
std::string body;
......@@ -1130,6 +1132,60 @@ TEST(ExportSessions, InboundMegolmSessions)
ASSERT_EQ(restored_output_str, secret_message);
}
TEST(Encryption, EncryptedFile)
{
std::string plaintext = "This is some plain text payload";
auto encryption_data = mtx::crypto::encrypt_file(plaintext);
ASSERT_NE(plaintext, mtx::crypto::to_string(encryption_data.first));
ASSERT_EQ(plaintext,
mtx::crypto::to_string(mtx::crypto::decrypt_file(
mtx::crypto::to_string(encryption_data.first), encryption_data.second)));
json j = R"({
"type": "m.room.message",
"content": {
"body": "test.txt",
"info": {
"size": 8,
"mimetype": "text/plain"
},
"msgtype": "m.file",
"file": {
"v": "v2",
"key": {
"alg": "A256CTR",
"ext": true,
"k": "6osKLzUKV1YZ06WEX0b77D784Te8oAj5eNU-gAgkjs4",
"key_ops": [
"encrypt",
"decrypt"
],
"kty": "oct"
},
"iv": "7zRP/t89YWcAAAAAAAAAAA",
"hashes": {
"sha256": "5g41hn7n10sCw3+2j7CQ9SJl6R/v5EBT4MshdFgHhzo"
},
"url": "mxc://neko.dev/WPKoOAPfPlcHiZZTEoaIoZhN",
"mimetype": "text/plain"
}
},
"event_id": "$1575320135447DEPky:neko.dev",
"origin_server_ts": 1575320135324,
"sender": "@test:neko.dev",
"unsigned": {
"age": 1081,
"transaction_id": "m1575320142400.8"
},
"room_id": "!YnUlhwgbBaGcAFsJOJ:neko.dev"
})"_json;
mtx::events::RoomEvent<mtx::events::msg::File> ev = j;
ASSERT_EQ("abcdefg\n",
mtx::crypto::to_string(mtx::crypto::decrypt_file("=\xFDX\xAB\xCA\xEB\x8F\xFF",
ev.content.file.value())));
}
TEST(Encryption, DISABLED_HandleRoomKeyEvent) {}
TEST(Encryption, DISABLED_HandleRoomKeyRequestEvent) {}
TEST(Encryption, DISABLED_HandleNewDevices) {}
......
......@@ -144,7 +144,7 @@ TEST(RoomEvents, FileMessage)
TEST(RoomEvents, EncryptedImageMessage)
{
json data = R"(
json data = R"(
{
"content": {
"body": "something-important.jpg",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment