Introduction to OOP (ValidPointer<T>)
- Due Oct 7, 2019 by 10pm
- Points 0
- Submitting a file upload
- File Types cpp
Description
This lab is designed to build a small class to begin getting comfortable with object-oriented programming in C++. The class we will build is designed to ensure the data we are using has not been modified in some manner. A class like this can be useful for working with untrusted parties and/or on untrusted machines. If we perform an action (e.g., digitally signing a contract), we would like to ensure that we do so with known data. It would be very scary if the contract we're signing could change after placing our signature on the document.
A first class to get the idea
We will make a starter class called ValidString. The ValidString class will receive a string to validate. The class will keep track of whether the string has been modified. In order to accomplish this behavior, the ValidString class will support the following methods:
- a constructor
- isValid()
The constructor
The constructor receives a pointer to the string that needs to be tracked. When the constructor is called, the class needs to look at the string's data. A simple solution to this problem is to copy the contents of the string to your ValidString class. From that point on, you could compare the contents you kept to the current string. If they are different, then you know the string has been modified. If they are the same, then you know the string hasn't been modified. This could work for short strings, but imagine larger pieces of data (e.g., a book to be published). We probably wouldn't want to duplicate the entire string and we'll need another method.
A cryptographic hash function
The solution we will use is a core foundation of the security community: a cryptographic hash function, or hash function for short. The way a hash function works is that it takes a large piece of data as input and converts it to a smaller string (i.e., hash value) that represents the original data. The hash function is created in such a way that any change in the data should get a different hash value. As a result, the hash value uniquely represents the input data in a much shorter space.
Good hash functions are difficult to create, so we will use one that is already installed on your system in a library of routines called sodium. A library is a set of routines that we can reuse without writing them to begin with. Examples from the standard library include getline, vector.push_back, and fstream.open.
libsodium
To use the sodium library, we need to use the following line near the beginning of our file
#include <sodium.h>
and then we need to call the proper routines in the sodium library. The first routine we must call is sodium_init, which sets up the library to be used properly. This can be the first thing we do in our program. The function returns a negative value if initializing the sodium library fails. The minimum possible program that uses sodium looks like:
#include <sodium.h>
int main() {
if (sodium_init() < 0) {
return 1;
}
return 0;
}
To tell the compiler where to find the sodium library, we need to add another argument for g++: -lsodium. For example, if the above file was named example.cpp, we would compile it with the following command:
g++ example.cpp -o example -lsodium
The specific hash function we want to call is named crypto_generichash. The function signature for crypto_generichash is
int crypto_generichash(unsigned char *out, size_t outlen,
const unsigned char *in, unsigned long long inlen,
const unsigned char *key, size_t keylen);
and is fully documented at the libsodium website. Some of this might seem gibberish, but we will walk through each piece individually. The first parameter is a pointer to the memory that should store the final hash value. You need to create this memory and then pass a pointer to this new memory location. The second parameter is the length of the hash value; the libsodium documentation recommends using the value crypto_generichash_BYTES. The third parameter is a pointer to the data for the hash function to evaluate; the fourth parameter is how big the data is. The fifth and sixth parameters can be ignored for our purposes. You can pass a nullptr and 0 for those last two values. Our expanded example would look like this:
#include <iostream>
#include <vector>
#include <string>
#include <sodium.h>
using namespace std;
int main() {
if (sodium_init() < 0) {
return 1;
}
vector<unsigned char> hash_value(crypto_generichash_BYTES);
string my_string;
cin >> my_string;
crypto_generichash(hash_value.data(), hash_value.size(),
my_string.data(), my_string.size(),
nullptr, 0);
return 0;
}
Now hash_value stores our unique identifier for the input stored in my_string.
Completing the constructor
When the ValidString constructor is called, you should use crypto_generichash to compute a unique hash value and store the hash value as a data member of the newly constructed ValidString. You should also store the pointer to the string so that you can access it in the future.
The isValid method
Whenever the isValid method is called, you should recompute the hash value of the current string and compare the new hash value to the stored hash value. You should return whether the two hash values are equal.
Example ValidString usage
The following example shows how the ValidString class can be used
int main() {
string my_string;
cin >> my_string;
ValidString my_valid_string(&my_string);
cout << my_valid_string.isValid() << "\n"; // prints 1
my_string += "additional text";
cout << my_valid_string.isValid() << "\n"; // prints 0
return 0;
}
The first value printed should be 1 (true) because the string has not been modified, but the second value printed should be 0 (false) because the string HAS been modified.
The complete ValidPointer<T> class
After you have built the ValidString class, you should create a more complete class that can point to any data type--not just a string. As long as the data pointed to is unchanged, you should get a true return value from isValid.
The more difficult portions of this lab are:
- You need to use templates to have your class work for any type. Maybe it is helpful to start with another concrete type (e.g., int, double, etc.) before going ahead with templates first thing. I find it is easier to modify a non-templated class that works to start using templates than it is to create a templated class from scratch.
- You will need to figure out how to get the compiler to help you figure out the size of the value being pointed to by T. For instance, if T is int, you will probably have 4 bytes of data to check, but if T is a string, you will probably have 32 bytes of data to check.
- This lab will likely take longer than all the previous labs, so you need to exercise patience if you are not done as quickly as you have been with past labs. The labs are going to start getting longer from this point on as they get more complicated. This will probably be the new normal.