Skip to content

Commit

Permalink
revamp crypto
Browse files Browse the repository at this point in the history
  • Loading branch information
Shengtong Zhang committed Jun 29, 2022
1 parent b72388b commit 7a2356d
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 162 deletions.
1 change: 1 addition & 0 deletions daemon/crypto/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ cc_test(
],
deps = [
":crypto_lib",
"//daemon/identifier:identifier",
"@com_google_googletest//:gtest_main",
],
)
108 changes: 38 additions & 70 deletions daemon/crypto/crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ auto generate_kx_keypair() -> std::pair<string, string> {
string(reinterpret_cast<char*>(secret_key), crypto_kx_SECRETKEYBYTES)};
}

auto generate_friend_request_keypair() -> std::pair<string, string> {
auto generate_invitation_keypair() -> std::pair<string, string> {
unsigned char public_key[crypto_box_PUBLICKEYBYTES];
unsigned char secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(public_key, secret_key);
Expand Down Expand Up @@ -275,16 +275,16 @@ auto decrypt_ack(const string& ciphertext, const string& read_key)

// NEW: generate / decode user ID.
// user ID is a string stored on the server that encompases username,
// allocation, kx_public_key, and friend_request_public_key
// allocation, kx_public_key, and invitation_public_key
// we use Base64 encoding as before
// TODO: what is included here is an IMPORTANT DECISION pending discussion.
// ASSUMPTION: username should not contain '@'
auto generate_user_id(const string& username, int allocation,
const string& kx_public_key,
const string& friend_request_public_key)
const string& invitation_public_key)
-> asphr::StatusOr<string> {
string kx_public_key_b64;
string friend_request_public_key_b64;
string invitation_public_key_b64;
// encode them using libsodium b64
kx_public_key_b64.resize(sodium_base64_ENCODED_LEN(
kx_public_key.size(), sodium_base64_VARIANT_URLSAFE_NO_PADDING));
Expand All @@ -294,23 +294,20 @@ auto generate_user_id(const string& username, int allocation,
reinterpret_cast<const unsigned char*>(kx_public_key.data()),
kx_public_key.size(), sodium_base64_VARIANT_URLSAFE_NO_PADDING);
// repeat for public key
friend_request_public_key_b64.resize(
sodium_base64_ENCODED_LEN(friend_request_public_key.size(),
sodium_base64_VARIANT_URLSAFE_NO_PADDING));
invitation_public_key_b64.resize(sodium_base64_ENCODED_LEN(
invitation_public_key.size(), sodium_base64_VARIANT_URLSAFE_NO_PADDING));
sodium_bin2base64(
friend_request_public_key_b64.data(),
friend_request_public_key_b64.size(),
reinterpret_cast<const unsigned char*>(friend_request_public_key.data()),
friend_request_public_key.size(),
sodium_base64_VARIANT_URLSAFE_NO_PADDING);
invitation_public_key_b64.data(), invitation_public_key_b64.size(),
reinterpret_cast<const unsigned char*>(invitation_public_key.data()),
invitation_public_key.size(), sodium_base64_VARIANT_URLSAFE_NO_PADDING);
// assert that username is well formed
if (username.find('@') != string::npos) {
return asphr::InvalidArgumentError("username cannot contain '@'");
}
// construct user ID
// return final string, separated by @
return username + "@" + std::to_string(allocation) + "@" + kx_public_key_b64 +
"@" + friend_request_public_key_b64;
"@" + invitation_public_key_b64;
}

auto split(const string& s, char del) -> vector<string> {
Expand All @@ -335,14 +332,14 @@ auto decode_user_id(const string& user_id)
}
// decode the public keys
string kx_public_key_b64 = user_id_split[2];
string friend_request_public_key_b64 = user_id_split[3];
string invitation_public_key_b64 = user_id_split[3];
string kx_public_key;
string friend_request_public_key;
string invitation_public_key;
size_t kx_public_key_len;
size_t friend_request_public_key_len;
size_t invitation_public_key_len;
const char* b64_end;
kx_public_key.resize(kx_public_key_b64.size());
friend_request_public_key.resize(friend_request_public_key_b64.size());
invitation_public_key.resize(invitation_public_key_b64.size());
if (sodium_base642bin(reinterpret_cast<unsigned char*>(kx_public_key.data()),
kx_public_key.size(), kx_public_key_b64.data(),
kx_public_key_b64.size(), "", &kx_public_key_len,
Expand All @@ -351,51 +348,42 @@ auto decode_user_id(const string& user_id)
return absl::UnknownError("failed to decode kx_public_key");
}
if (sodium_base642bin(
reinterpret_cast<unsigned char*>(friend_request_public_key.data()),
friend_request_public_key.size(),
friend_request_public_key_b64.data(),
friend_request_public_key_b64.size(), "",
&friend_request_public_key_len, &b64_end,
sodium_base64_VARIANT_URLSAFE_NO_PADDING) != 0) {
reinterpret_cast<unsigned char*>(invitation_public_key.data()),
invitation_public_key.size(), invitation_public_key_b64.data(),
invitation_public_key_b64.size(), "", &invitation_public_key_len,
&b64_end, sodium_base64_VARIANT_URLSAFE_NO_PADDING) != 0) {
return absl::UnknownError("failed to decode kx_public_key");
}
kx_public_key.resize(kx_public_key_len);
friend_request_public_key.resize(friend_request_public_key_len);
invitation_public_key.resize(invitation_public_key_len);
// return the decoded values
return std::make_tuple(user_id_split[0], std::stoi(user_id_split[1]),
kx_public_key, friend_request_public_key);
kx_public_key, invitation_public_key);
}

// encrypt an asynchronous friend request
// The async friend request from A->B is Enc(ID_B, ID_A || msg) =
// = Enc(f_sk_A, f_pk_B, ID_A || msg)
// Note: there's a lot of redundant information here
// TODO: optimize message length
auto encrypt_async_friend_request(const string& self_id,
const string& self_friend_request_private_key,
const string& friend_id,
const string& message)
auto encrypt_async_invitation(const string& self_id,
const string& self_invitation_private_key,
const string& friend_invitation_public_key,
const string& message)
-> asphr::StatusOr<string> {
// decode the keys
auto friend_keys_ = crypto::decode_user_id(friend_id);
if (!friend_keys_.ok()) {
return friend_keys_.status();
}
// get the keys
// decode the friend_id
string friend_friend_request_public_key = std::get<3>(friend_keys_.value());

if (friend_friend_request_public_key.size() != crypto_box_PUBLICKEYBYTES) {
// check integrity of the keys
if (friend_invitation_public_key.size() != crypto_box_PUBLICKEYBYTES) {
return asphr::InvalidArgumentError(
"friend_public_key is not the correct size");
}
if (self_friend_request_private_key.size() != crypto_box_SECRETKEYBYTES) {
if (self_invitation_private_key.size() != crypto_box_SECRETKEYBYTES) {
return asphr::InvalidArgumentError(
"self_private_key is not the correct size");
}
// formmat the message we want to send
// assert message does not contain '|'
// assert message and self_id does not contain '|'
assert(message.find('|') == string::npos);
assert(self_id.find('|') == string::npos);
string message_to_send = self_id + "|" + message;

if (message_to_send.size() > GUARANTEED_SINGLE_MESSAGE_SIZE) {
Expand Down Expand Up @@ -436,9 +424,9 @@ auto encrypt_async_friend_request(const string& self_id,
reinterpret_cast<const unsigned char*>(plaintext.data()),
plaintext.size(), nonce,
reinterpret_cast<const unsigned char*>(
friend_friend_request_public_key.data()),
friend_invitation_public_key.data()),
reinterpret_cast<const unsigned char*>(
self_friend_request_private_key.data())) != 0) {
self_invitation_private_key.data())) != 0) {
return absl::UnknownError("failed to encrypt message");
}
// append the nounce to the end of the ciphertext
Expand All @@ -460,37 +448,17 @@ auto encrypt_async_friend_request(const string& self_id,
}

// decrypt the asynchronous friend requests
// returns the ID_STRING and the MESSAGE
// or create a new one.
// I'm using the second plan for now.
auto decrypt_async_friend_request(const string& self_id,
const string& self_friend_request_private_key,
const string& friend_id,
const string& ciphertext)
-> asphr::StatusOr<pair<string, string>> {
// We now decrypt the message, using a clone of the code above
// decode the keys
auto friend_keys_ = crypto::decode_user_id(friend_id);
if (!friend_keys_.ok()) {
return friend_keys_.status();
}
// get the keys
string friend_friend_request_public_key = std::get<3>(friend_keys_.value());
return crypto::decrypt_async_friend_request_public_key_only(
self_id, self_friend_request_private_key,
friend_friend_request_public_key, ciphertext);
}

auto decrypt_async_friend_request_public_key_only(
const string& self_id, const string& self_friend_request_private_key,
const string& friend_friend_request_public_key, const string& ciphertext)
auto decrypt_async_invitation(const string& self_invitation_private_key,
const string& friend_invitation_public_key,
const string& ciphertext)
-> asphr::StatusOr<pair<string, string>> {
// We now decrypt the message, using a clone of the code above
if (friend_friend_request_public_key.size() != crypto_box_PUBLICKEYBYTES) {
if (friend_invitation_public_key.size() != crypto_box_PUBLICKEYBYTES) {
return asphr::InvalidArgumentError(
"friend_public_key is not the correct size");
}
if (self_friend_request_private_key.size() != crypto_box_SECRETKEYBYTES) {
if (self_invitation_private_key.size() != crypto_box_SECRETKEYBYTES) {
return asphr::InvalidArgumentError(
"self_private_key is not the correct size");
}
Expand Down Expand Up @@ -520,9 +488,9 @@ auto decrypt_async_friend_request_public_key_only(
reinterpret_cast<const unsigned char*>(ciphertext_str.data()),
ciphertext_str.size(), nonce,
reinterpret_cast<const unsigned char*>(
friend_friend_request_public_key.data()),
friend_invitation_public_key.data()),
reinterpret_cast<const unsigned char*>(
self_friend_request_private_key.data())) != 0) {
self_invitation_private_key.data())) != 0) {
return absl::UnknownError("failed to decrypt friend request");
}
assert(padded_plaintext_len == plaintext.size());
Expand Down
38 changes: 8 additions & 30 deletions daemon/crypto/crypto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ auto generic_hash(string_view data) -> std::string;
// Generates a new keypair in the format <public_key, private_key>.
auto generate_kx_keypair() -> std::pair<string, string>;

auto generate_friend_request_keypair() -> std::pair<string, string>;
auto generate_invitation_keypair() -> std::pair<string, string>;

auto derive_read_write_keys(string my_public_key, string my_private_key,
string friend_public_key)
Expand Down Expand Up @@ -65,39 +65,17 @@ auto encrypt_ack(uint32_t ack_id, const string& write_key)
auto decrypt_ack(const string& ciphertext, const string& read_key)
-> asphr::StatusOr<uint32_t>;

// NEW: generate / decode user ID.
// user ID is a string stored on the server
// that encompases username, allocation, kx_public_key, and
// friend_request_public_key
auto generate_user_id(const string& username, int allocation,
const string& kx_public_key,
const string& friend_request_public_key)
-> asphr::StatusOr<string>;

auto decode_user_id(const string& user_id)
-> asphr::StatusOr<std::tuple<string, int, string, string>>;

// encrypt an asynchronous friend request
// The async friend request from A->B is Enc(ID_B, ID_A || msg) = Enc(f_pk_B,
// ID_A || msg)
auto encrypt_async_friend_request(const string& self_id,
const string& self_friend_request_private_key,
const string& friend_id,
const string& message)
-> asphr::StatusOr<string>;

// decrypt the asynchronous friend requests
// returns the NAME, ALLOCATION INDEX, KX PUBLIC KEY, MSG
// in that order
auto decrypt_async_friend_request(const string& self_id,
const string& self_friend_request_private_key,
const string& friend_id,
const string& ciphertext)
-> asphr::StatusOr<pair<string, string>>;
auto encrypt_async_invitation(const string& self_id,
const string& self_invitation_private_key,
const string& friend_invitation_public_key,
const string& message) -> asphr::StatusOr<string>;

// this method is needed for transmission retrieval
auto decrypt_async_friend_request_public_key_only(
const string& self_id, const string& self_friend_request_private_key,
const string& friend_friend_request_public_key, const string& ciphertext)
auto decrypt_async_invitation(const string& self_invitation_private_key,
const string& friend_invitation_public_key,
const string& ciphertext)
-> asphr::StatusOr<pair<string, string>>;
}; // namespace crypto
66 changes: 27 additions & 39 deletions daemon/crypto/crypto_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include <gtest/gtest.h>

#include "daemon/identifier/identifier.hpp"

TEST(CryptoTest, EncryptDecrypt) {
crypto::init();

Expand Down Expand Up @@ -116,66 +118,52 @@ TEST(CryptoTest, EncryptDecryptAcks) {
}
}

TEST(CryptoTest, EncodeDecodeId) {
for (auto round = 0; round < 10; round++) {
string user = "user_asdf_" + to_string(round);
auto [kx_pk, kx_sk] = crypto::generate_kx_keypair();
auto [f_pk, f_sk] = crypto::generate_friend_request_keypair();
int allocation = rand() % 10000;
auto id_ = crypto::generate_user_id(user, allocation, kx_pk, f_pk);
EXPECT_TRUE(id_.ok());
auto dec = crypto::decode_user_id(id_.value());
EXPECT_TRUE(dec.ok());
auto [user_, allocation_, kx_pk_, f_pk_] = dec.value();
EXPECT_EQ(user, user_);
EXPECT_EQ(allocation, allocation_);
EXPECT_EQ(kx_pk, kx_pk_);
EXPECT_EQ(f_pk, f_pk_);
}
}

TEST(CryptoTest, EncryptDecryptAsyncFriendRequest) {
for (auto round = 0; round < 10; round++) {
string user1 = "user1_" + to_string(round);
string user2 = "user2_" + to_string(round);
auto [kx_pk1, kx_sk1] = crypto::generate_kx_keypair();
auto [kx_pk2, kx_sk2] = crypto::generate_kx_keypair();
auto [f_pk1, f_sk1] = crypto::generate_friend_request_keypair();
auto [f_pk2, f_sk2] = crypto::generate_friend_request_keypair();
auto [f_pk1, f_sk1] = crypto::generate_invitation_keypair();
auto [f_pk2, f_sk2] = crypto::generate_invitation_keypair();

cout << "Keypair generated" << endl;

int allocation1 = rand() % 10000;
int allocation2 = rand() % 10000;
auto id1_ = crypto::generate_user_id(user1, allocation1, kx_pk1, f_pk1);
auto id2_ = crypto::generate_user_id(user2, allocation2, kx_pk2, f_pk2);
if (!id1_.ok() || !id2_.ok()) {
FAIL() << "Failed to generate user IDs";
}
auto id1 = id1_.value();
auto id2 = id2_.value();
// Create public ids
auto id1 = PublicIdentifier(allocation1, kx_pk1, f_pk1).to_public_id();

cout << "Id generated: " << id1 << endl;

string message = "hello from 1 to 2 on round " + to_string(round);
// user 1 sends friend request to user 2
auto friend_request_ =
crypto::encrypt_async_friend_request(id1, f_sk1, id2, message);
if (!friend_request_.ok()) {
// note: This API have been changed in arvid's update.
auto invitation_ =
crypto::encrypt_async_invitation(id1, f_sk1, f_pk2, message);
if (!invitation_.ok()) {
FAIL() << "Failed to encrypt friend request";
}
auto friend_request = friend_request_.value();
auto invitation = invitation_.value();

cout << "Friend request encrypted" << endl;
// user 2 decrypts friend request
auto decrypted_friend_request_ =
crypto::decrypt_async_friend_request(id2, f_sk2, id1, friend_request);
if (!decrypted_friend_request_.ok()) {
cout << decrypted_friend_request_.status() << endl;
// Note: This API have been changed in arvid's update.
auto decrypted_invitation_ =
crypto::decrypt_async_invitation(f_sk2, f_pk1, invitation);
if (!decrypted_invitation_.ok()) {
cout << decrypted_invitation_.status() << endl;
FAIL() << "Failed to decrypt friend request";
}
auto decrypted_friend_request = decrypted_friend_request_.value();
EXPECT_EQ(decrypted_friend_request.first, id1);
EXPECT_EQ(decrypted_friend_request.second, message);
auto decrypted_invitation = decrypted_invitation_.value();
EXPECT_EQ(decrypted_invitation.first, id1);
EXPECT_EQ(decrypted_invitation.second, message);
// user 2 then decrypts the id to get the user1's kx key.
auto identifier_ =
PublicIdentifier::from_public_id(decrypted_invitation.first);
assert(identifier_.ok());
auto identifier = identifier_.value();
assert(identifier.index == allocation1);
assert(identifier.kx_public_key == kx_pk1);
assert(identifier.invitation_public_key == f_pk1);
}
}
Loading

0 comments on commit 7a2356d

Please sign in to comment.