Educational Objectives: After completing this assignment, the student should be able to accomplish the following:
Operational Objectives: Write a server providing password services, including password verification, password change, and creation and removal of user accounts. The official development/testing/assessment environment is: gcc version 4.1.1 20070105 (Red Hat 4.1.1-52). This is the environment on the linprog machines.
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 item 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). In fact, because the other three require file I/O, they are stuck with Ω(n) runtime in any case, so there is little point in trying to make the internal algorithm more efficient than Θ(n). (Make sure you understand this paragraph, including the uses Big Theta, Big O, and Big Omega..)
The class PWServer is defined in the file pwserver.h. The (public) interface of a PWServer object is as follows:
PWServer (const char* infile, const char* outfile, unsigned int max);
This is the only constructor provided for the class. There are three
parameters required:
Once a PWServer object is created, these data are fixed for that object and cannot be changed during the lifetime of the object.
~PWServer ();
This is the class destructor which ensures correct destruction of each PWServer
object. The destructor is not called explicitly by the client program, rather it
is called automatically when the object goes out of scope.
int CheckPW (const char* uid, char* upw);
This method provides the most basic password service: it takes a user name
uid and a user password upw and checks the validity of the
password. The value 1 is returned if the user is authenticated and the value 0
is returned if the user is not authenticated.
Note that the second parameter which holds the password in clear text is not a "const" pointer. This allows the server to overwrite this string as soon as it is copied into a local string object, which is used as input to the secure hashing algorithm creating an associated signature. An encryption scheme could be used for this parameter to further complicate the theft of the password.
int ChangePW (const char* uid, char* upw, char* newpw);
Along with CheckPW, this is the other user accessible service: changing the
password. The three parameters required are the user name uid, the current password
upw, and the new password newpw. The service first
authenticates the user with the current password, then changes to the new
password. Both the old and new password strings are overwritten as soon as
possible by the server object. Return value 1 indicates success while return value 0
indicates failure.
int CreateUser (const char* uid, char* upw);
This and the following service are intended for use by the system management
only. They are used to create new users and remove current users from the
system. Typically, authorization to access CreateUser would be provided
by system management to a specific user. This is a potential weakness because ad
hoc identification and authentification methods are often used, for example in
the FSU CS system, the ID number is matched with the official list of CS
majors. It is not impossible for identity theft to take place at this stage of
new account generation.
Again, two parameters must be supplied, user name uid and the proposed password upw, and the value 1 is returned when the request is successful and 0 is returned when it is not successful.
int DeleteUser (const char* uid);
This is
typically accessible only by system management and has the obvious
functionality: a user name uid is purged from the authentication
system. Return value 1 indicates success while return value 0 indicates
failure.
Note that a system manager could "disable" a user by simply changing the hash value stored in the password file, so that authentication would fail. Recovering from such "disablement" would require another service be added to the server interface, effectively a non-password-protected ChangePW().
void Dump (std::ostream& out1 = std::cout);
This method is provided only during "white box" testing of the server. It
displays the internal contents of the server object. Note that the parameter is
a std::ostream& with a default value std::cout, so that
Dump() may be called with no explicit arguments and the results
will be sent to standard output.
The interaction of a PWServer object with its files follows a few simple rules:
The third constructor parameter is used to limit the number of users in the system that are served by a given PWServer object.
Work within your subdirectory called cop4530/hw2.
Begin by copying the following files into your project directory:
LIB/hw2/pwclient.cpp LIB/hw2/pwserver.eg LIB/hw2/pwf1.1 // NOTE: password = username in this file LIB/hw2/pwf1.2 // NOTE: password = username in this file LIB/hw2/hw2submit.sh
For this project you need to create three more files: pwserver.h, pwserver.cpp, and makefile. All three should be placed in the hw2 directory.
Turn in the project files using the hw2submit.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* uid, char* upw); // return 1 if <uid , upw> is authentic, 0 otherwise int ChangePW (const char* uid, char* upw, char* newpw); // return 1 if user password is successfully updated, 0 otherwise int CreateUser (const char* uid, char* upw); // return 1 if new user is successfully entered into system, 0 otherwise int DeleteUser (const char* uid); // return 1 if user is sucessfully deleted from system, 0 otherwise void Dump(std::ostream& out1 = std::cout); // public, intended for white-box testing
and should read information from a file upon instantiation and write to (a possibly different) file upon un-instantiation. Parameters uid, upw, 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 uid-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 uid-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 hw2/pwclient.cpp.
A PWServer object should store its uid-signature data in a private std::vector<T> object and use binary search to look up uid-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 uid-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 std::vector<T>. Binary search in a std::vector<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* uid, char* upw) // return 1 iff the signature matches the user id // algorithm: // 1. let uidupw = uid + upw ("+" = concatenate) // 2. let sgn = hash(uidupw) // 3. search for (uid, sgn) in vector // 4. if not found return 0 // 5. if sgn != stored signature return 0 // 6. return 1 int PWServer::ChangePW (const char* uid, char* upw char* newpw) // return 1 iff user signature is successfully updated // algorithm: // 1 - 5 same as above // 6. let newsgn = hash(uid + 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* uid, char* upw) // return 1 iff new user,signature is successfully updated // algorithm: // 1 - 3 same as above // 4. if found return 0 // 5. insert (uid,sgn) into vector (return 0 if failure) // 6. update password file (return 0 if failure) // 7. return 1 int PWServer::DeleteUser (const char* uid) // return 1 iff user is sucessfully deleted // algorithm: // 1. search for (uid,***) 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 upw with a generic character as soon as it is used, so that this is not left in memory longer than necessary.
Note that the test client requires two file names as command line input. This is where you specify the names of the input and output files for the server.
Working executables pwclient_i.x and pwclient_s.x are distributed in LIB/area51. ("_i" is for Intel/Linux, "_s" is for Sun/Unix.)