Educational Objectives: Further experience in design and implementation of C++ classes, use of the template class TVector<>, and project management using make; implementation of binary search algorithm; use of hash functions; introduction to computer security.
Operational Objectives: Write a server providing password services, including password verification, password change, and creation and removal of user accounts.
Deliverables: Three files: pwserver.h, pwserver.cpp, and makefile.
The class PWServer conforms to a standard model for controlled access to computer services known as the IAA model, where access is controlled via a three-step process:
Note the distinction between hashing and encryption. You cannot "decode" a hash value, because a hash function is not reversible. In contrast, an encrypted must be de-crypted in order to be read. For more about hashing, see the lecture notes Hashing, hash Functions, and Their Uses.
The intended use of a password server is to provide the identification and authentication phases of the model. There are four basic services it should provide:
The first two services are typically provided to all users of a system. The latter two may be reserved for use by system administrators.
A user password should NEVER be stored on disk and should be retained in memory as little as possible. To check a user password, the password signature is stored. In general, the signature is a hash value obtained from the user password and other user information. Specifically, our signature will be the hash value of the string consisting of the username and password concatenated, using the Marsaglia Mixer hash function.
To get really excellent security, you would use a more elaborate hash function such as the Secure Hashing Algorithm (SHA) designed by NIST for secure electronic signatures. (For more info, see: Applied Cryptography --Protocols, Algorithms, and Source Code in C by Bruce Schneider.)
If the advice above is followed, there are no technical security issues remaining for the password server itself. However, it is still very easy to compromise security:
Note that the first two of these are human, rather than technical, breaches. The third may be closed by running a secure network, encrypting passwords while on the network, or running the password server locally at the location of the user.
The basic identification/authentication (check username/password) service is used often by all users, so it should be fast. Typical specifications for the runtime of this service are O(log n) or better, where n is the number of users. The other three services are used much less often, and it is acceptable that they have longer runtimes, typically specified to be O(n) or better.
Work within your subdirectory called cop4530/proj1.
Begin by copying the following files into your project directory:
LIB/proj1/pwtest.cpp LIB/proj1/pwserver.eg LIB/proj1/pwf1.1 LIB/proj1/pwf1.2 LIB/submitscripts/proj1submit.sh
For this project you need to create three more files: pwserver.h, pwserver.cpp, and makefile. All three should be placed in the proj1 directory.
Turn in the the project files using the proj1submit.sh submit script.
Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu to submit this assignment. If you do not receive the second confirmation with the contents of your project, there has been a malfunction.
The file pwserver.h must contain the definition of the class PWServer and pwserver.cpp must contain the implementation of the PWServer methods.
The class PWServer should provide the following services through its public interface:
int CheckPW (const char* userid, char* userpw); // return 1 if <userid , userpw> is authentic, 0 otherwise int ChangePW (const char* userid, char* userpw, char* newpw); // return 1 if user password is successfully updated, 0 otherwise int CreateUser (const char* userid, char* userpw); // return 1 if new user is successfully entered into system, 0 otherwise int DeleteUser (const char* userid); // return 1 if user is sucessfully deleted from system, 0 otherwiseand should read information from a file upon instantiation and write to (a possibly different) file upon un-instantiation. Parameters userid, userpw, and newpw are the user name, password, and new password, all of type char* or const char*. The services return 1 for success and 0 for unsuccess. There is no direct user interaction by PWServer objects. (The server should of course send error messages through the std::cerr object.)
The PWServer constructor should take three parameters: the input and output filenames (both const char*) from which to read and write the userid-signature data and the maximum number (unsigned int) of users allowed. The infile should be read upon instantiation. The outfile should be (re-)written whenever a change is made in the userid-signature data and upon un-instantiation. Both files should be kept unattached except when needed.
The file structure assumed by PWServer objects is as follows.
7 burns 24240 gaitros 57423 lacher 39884 porter 9041 rupert 18097 tarokh 20692 toh 63429
Your solution is required to run with the supplied client program proj1/pwtest.cpp.
A PWServer object should store its userid-signature data in a private TVector<T> object and use binary search to look up userid-signature data in this object. Thus basic authentication should have runtime <= O(log size). In the implementation documentation, give a brief argument why CheckPW() has runtime <= O(log size).
Insertion and removal of userid-signature pairs should maintain the sorted order of this vector and should have runtime <= O(size). In the implementation documentation, give a brief argument why ChangePW(), CreateUser(), and DeleteUser() all have runtime = Θ(size), and that this is the best that could be hope for. (Hint: files are involved.)
For assessment purposes, we all need to use the same hash function and file spec. We will use the hash function for strings that is based on the Marsaglia Mixer, discussed in lecture and released in our course library in cpp/hash.*. Note that this hash function may be applied to either C strings or String objects with the same result. Re-use the code in cpp/hash.* using include statements for the header file and separately compiling the source file. Do not re-code the hash functions.
You will need to design/define/implement the type T used in your TVector<T>. Binary search in a TVector<T> requires that the elements be in order. You may need to overload certain operators (such as operator < ()) for the type T.
Headers and implementation algorithms of four services would be something like the following:
int PWServer::CheckPW (const char* userid, char* userpw) // return 1 iff the signature matches the user id // algorithm: // 1. let useriduserpw = userid + userpw ("+" = concatenate) // 2. let sgn = hash(useriduserpw) // 3. search for (userid, sgn) in vector // 4. if not found return 0 // 5. if sgn != stored signature return 0 // 6. return 1 int PWServer::ChangePW (const char* userid, char* userpw char* newpw) // return 1 iff user signature is successfully updated // algorithm: // 1 - 5 same as above // 6. let newsgn = hash(userid + newpw) // 7. assign stored signature at this entry = newsgn // 8. update password file (return 0 if failure) // 9. return 1 int PWServer::CreateUser (const char* userid, char* userpw) // return 1 iff new user,signature is successfully updated // algorithm: // 1 - 3 same as above // 4. if found return 0 // 5. insert (userid,sgn) into vector (return 0 if failure) // 6. update password file (return 0 if failure) // 8. return 1 int PWServer::DeleteUser (const char* userid) // return 1 iff user is sucessfully deleted // algorithm: // 1. Search for (userid,***) in vector // 2. if not found return 0 // 3. remove pair from vector // 4. update password file (return 0 if failure) // 5. return 1
Because the locational index is used more than once in the implementation of a service, it would make sense to have a private search method that implements binary search (specifically, the lower_bound algorithm) on the vector and stores the result of the search (an index value) in a private data item for the use of the remainder of the algorithm. This way only one call to the search algorithm is made in implementing a service.
Another good idea is to overwrite the clear text userpw with a generic character as soon as it is used, so that this is not left in memory longer than necessary.
Working executables pwtest_s.x and pwtest_i.x are distributed in LIB/area54.