Deadline: 12th of Ordibehesht - 23:59 o'clock
In this homework, you will design and implement a simple messenger system in C++. The system will support different types of messages (such as text and voice), and will allow users to send messages to each other securely using RSA cryptography. You will use the provided crypto.h
file for all cryptographic operations.
The question is divided into several parts:
- Message Hierarchy: Implement a base
Message
class and deriveTextMessage
andVoiceMessage
from it. - User and Server Classes: Implement the
User
andServer
classes, which interact to send, receive, and store messages. - Security: All messages must be authenticated using RSA digital signatures.
- Cryptography: Use the provided
crypto.h
interface for key generation, signing, and verification.
All messages share some common properties. Define a base class Message
with the following private member variables:
class Message {
public:
// (Member functions will be defined below)
private:
std::string type; // Type of the message ("text", "voice", ...)
std::string sender; // Username of the sender
std::string receiver; // Username of the receiver
std::string time; // Creation time in GMT, format: "Sun Nov 13 17:50:43 2022"
};
You must implement the following member functions (you may add const
, override
, etc. as needed, but do not change the function signatures):
-
Constructor:
Assigns all member variables excepttime
. Thetime
variable should be set to the current GMT time in the format"Sun Nov 13 17:50:43 2022"
. Use the<ctime>
library for this.Message(std::string type, std::string sender, std::string receiver);
-
Default Constructor:
Use constructor delegation to assign member variables.Message();
-
Getter Functions:
Since all member variables are private, provide getter functions for each:std::string get_type(); std::string get_sender(); std::string get_receiver(); std::string get_time();
-
Print Function:
Prints the message details to an output stream.void print(std::ostream &os);
Example output:
************************* david -> jenifer message type: text message time: Sun Nov 13 17:50:43 2022 *************************
-
Stream Insertion Operator:
Overload the<<
operator to print aMessage
using theprint
function.std::ostream& operator<<(std::ostream &os, const Message &c);
Question:
After implementing the derived classes, answer:
Why do you think we defined the print
function and didn't implement everything in operator<<
itself?
This class inherits from Message
and adds a text
member variable.
class TextMessage : public Message {
public:
// (Member functions will be defined below)
private:
std::string text;
};
-
Constructor:
Assigns all member variables.TextMessage(std::string text, std::string sender, std::string receiver);
-
Print Function:
Prints all message details, including the text.void print(std::ostream &os);
Example output:
************************* david -> jenifer message type: text message time: Sun Nov 13 17:50:43 2022 text: hello everybody *************************
-
Getter for Text:
std::string get_text();
This class inherits from Message
and adds a voice
member variable, which is a vector of 5 random bytes.
class VoiceMessage : public Message {
public:
// (Member functions will be defined below)
private:
std::vector<unsigned char> voice;
};
-
Constructor:
Assigns all member variables and fillsvoice
with 5 random bytes.VoiceMessage(std::string sender, std::string receiver);
-
Print Function:
Prints all message details, including the voice data as integers.void print(std::ostream &os);
Example output:
************************* david -> jenifer message type: voice message time: Sun Nov 13 17:50:43 2022 voice: 166 240 216 41 129 *************************
-
Getter for Voice:
std::vector<unsigned char> get_voice();
The User
and Server
classes are tightly coupled. Read all instructions before starting implementation.
Represents a user who can send messages. Each user has a username, a private key, and a pointer to the server.
class User {
public:
// (Member functions will be defined below)
private:
std::string username; // Username of the user
std::string private_key; // PEM-encoded private key
Server* const server; // Pointer to the server
};
-
Constructor:
Assigns all member variables.User(std::string username, std::string private_key, Server* server);
-
Getter for Username:
std::string get_username();
-
Send Text Message:
Sends a text message to another user. Returnstrue
if successful,false
otherwise.
Use thecreate_message
function of theServer
class.bool send_text_message(std::string text, std::string receiver);
-
Send Voice Message:
Sends a voice message (5 random bytes) to another user. Returnstrue
if successful,false
otherwise.
Use thecreate_message
function of theServer
class.bool send_voice_message(std::string receiver);
Responsible for storing users, public keys, and messages.
class Server {
public:
// (Member functions will be defined below)
private:
std::vector<User> users; // List of users
std::map<std::string, std::string> public_keys; // Map: username -> public key
std::vector<Message*> messages; // All messages sent
};
-
Default Constructor:
Server();
-
Getter Functions:
std::vector<User> get_users(); std::map<std::string, std::string> get_public_keys(); std::vector<Message*> get_messages();
-
Create User:
Creates a new user with a unique username. If the username already exists, throwstd::logic_error
.
Generates an RSA key pair for the user. The private key is given to the user; the public key is stored inpublic_keys
.User create_user(std::string username);
-
Create Message:
Adds a message to the server. The sender must sign their username as a signature.
The server authenticates the signature before adding the message.bool create_message(Message* msg, std::string signature);
-
Get All Messages From:
Returns all messages sent from a given username.
You must use STL algorithms only (no loops).std::vector<Message*> get_all_messages_from(std::string username);
-
Get All Messages To:
Returns all messages sent to a given username.
You must use STL algorithms only (no loops).std::vector<Message*> get_all_messages_to(std::string username);
-
Get Chat:
Returns all messages between two users (regardless of direction).
You must use STL algorithms only (no loops).std::vector<Message*> get_chat(std::string user1, std::string user2);
-
Sort Messages:
Sorts a vector of messages by their creation time.
You must use STL algorithms only (no loops).void sort_msgs(std::vector<Message*> msgs);
You are provided with a crypto.h
file that contains all the cryptographic functions you need. Do not implement your own cryptography! Use these functions as described.
std::string public_key, private_key;
crypto::generate_key(public_key, private_key);
public_key
andprivate_key
will be PEM-encoded strings.
To sign a string (e.g., a username) with a private key:
std::string signature = crypto::signMessage(private_key, "my data");
To verify a signature with a public key:
bool authentic = crypto::verifySignature(public_key, "my data", signature);
- Returns
true
if the signature is valid,false
otherwise.
RSA is a widely-used public-key cryptosystem that enables secure data transmission and digital signatures. It is based on the mathematical difficulty of factoring large integers.
- Key Generation:
- Each user generates a pair of keys: a public key (shared with others) and a private key (kept secret).
- Signing:
- To prove a message is from a specific user, the user signs the message (or some data, like their username) with their private key.
- Verification:
- Anyone with the user's public key can verify that the signature is valid and that the message was indeed signed by the owner of the private key.
- Authentication:
- Ensures that only the legitimate sender can send messages as themselves.
- Integrity:
- Guarantees that the message has not been tampered with.
- When a user sends a message, they sign their username with their private key.
- The server uses the sender's public key to verify the signature before accepting the message.
- Random Bytes: For
VoiceMessage
, use C++ random number generation to fill thevoice
vector with 5 random bytes. - Time Formatting: Use
std::time_t
,std::gmtime
, andstd::strftime
to format the time string. - STL Algorithms: For filtering and sorting messages, use algorithms like
std::copy_if
,std::sort
, etc. - Memory Management: Since
messages
is a vector of pointers, ensure you manage memory properly to avoid leaks.
// crypto.h
#ifndef CRYPTO_H
#define CRYPTO_H
#include <string>
namespace crypto {
/*
* Generate a 2048-bit RSA key pair.
* public_key <- PEM-encoded public key
* private_key <- PEM-encoded private key
*/
void generate_key(std::string& public_key, std::string& private_key);
/*
* Sign `data` using the PEM-encoded RSA private key.
* Returns a Base64-encoded signature.
*/
std::string signMessage(const std::string& private_key,
const std::string& data);
/*
* Verify a Base64-encoded signature over `data` using
* the PEM-encoded RSA public key. Returns true if valid.
*/
bool verifySignature(const std::string& public_key, const std::string& data,
const std::string& signature);
} // namespace crypto
#endif // CRYPTO_H
If you want to debug your code, set the if
statement to true
. This will allow you to place your debugging code in the designated section. Once you're done with the debugging process, remember to set the if
statement back to false
to test your program using the provided unit-test.cpp
.
#include <gtest/gtest.h>
#include <iostream>
#include "message.h"
#include "server.h"
#include "user.h"
int main(int argc, char **argv) {
if (true) // Set to false to run unit-tests
{
// Debug section: Place your debugging code here
} else {
::testing::InitGoogleTest(&argc, argv);
std::cout << "RUNNING TESTS ..." << std::endl;
int ret{RUN_ALL_TESTS()};
if (!ret)
std::cout << "<<<SUCCESS>>>" << std::endl;
else
std::cout << "FAILED" << std::endl;
}
return 0;
}
Best Regards, Hamidi