Homework 2: Password Server

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: g++ version 4.1.1 on the linprog machines.

Deliverables: Four files: pwserver.h, pwserver.cpp, makefile, and log.txt.

The Identification/Authentication/Authorization model for access control

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:

  1. Identification: The person or agent making a request for service is identified, in this situation by providing a user ID or username. (Note that this implies that the user ID uniquely identifies a user, so that user IDs are unique.)
  2. Authentication. Once the agent asserts an identity, this identity is verified, or authenticated, by an independent item of information, the password. The password should satisfy certain rules:
    1. The password is known to the agent, who has responsibility to keep it private.
    2. The password is not known to or stored by the authentication system.
    3. The password (together with the username) is hashed to an unsigned integer value by a hashing algorithm (aka hash function). The input to the hash function is called a key. The output of the hash function is called a hash value or, in this particular context, a signature.
    4. The hashing algorithm may be publicly known.
    5. The signatures associated with keys may also be publicly readable.
    6. The hashing algorithm maps keys to numbers in a way that is mathematically irreversible, so that no formula mapping signature back to the key that generated it is possible; and it is pragmatically impractical to guess a key from its signature.
  3. Authorization. Once an agent is identified and authenticated, a database of services to which that agent is entitled or permitted is accessed, and those services are made available.

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.

Password Servers

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:

  1. Check a user password for entry into the system
  2. Change a user password
  3. Create a new user
  4. Remove an existing user

The first two services are typically provided to all users of a system. The latter two may be reserved for use by system administrators.

Security Issues for a Password Server

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:

  1. The initial process of creating a new user: the password server cannot provide authentication at this stage, so the authentication of a new user is left to ad hoc system administration policies.
  2. A user may allow another person to know their password. This can happen intentionally or unintentionally.
  3. The communication between the login process and the password server may be unencrypted, allowing a user password to be read by another process on the network.

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.

Efficiency Issues for a Password Server

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 PWServer API

The class PWServer is defined in the file pwserver.h. The (public) interface of a PWServer object is as follows:

PWServer Files

The interaction of a PWServer object with its files follows a few simple rules:

  1. Each file begins with the number of usernames contained in the file and is followed by that number of lines. Each of these subsequent lines contains a username followed by an integer, the signature for that username. These files cannot be created with an editor, because the signature depends on the username, the user password (known only to the user), and the hashing algorithm.
  2. The input file (whose name is the first constructor argument) is read one time to initialize the object. This file is closed after the initial read and never disturbed again. It therefore remains as a record of how the object was initialized.
  3. The output file (whose name is the second constructor argument) is opened, written, and closed whenever the password file data is changed in the PWServer object. It is also written by the destructor. Therefore this file serves as a record of the state of the PWServer object at the time it goes out of existence.
  4. A typical use scenario would be to stop the current PWServer object at midnight, copy its output file to the next day's startup file, and then start another PWServer object. An appropriate naming scheme for the files serves as a daily record of changes in the system user list and their signatures.
  5. You can create your own pw files using pwclient.x and the CreateUser option.

The third constructor parameter is used to limit the number of users in the system that are served by a given PWServer object.

Procedural Requirements

  1. Work within your subdirectory called cop4530/hw2.

  2. Maintain an ascii (text) file named log.txt with entries giving date, time, duration and task description for all work on this project. Example entries:

    01-25-10, 1:30pm, 2 hours
    -------------------------
    - copied distributed files, initialized all other project files
    - experimented extensively with the area51 executables - believe I understand how they work
    - studied the material on authentication and the difference between
      encryption to produce secure messages and hashing to produce secure signatures.
    
    01-26-10, 1:30pm, 1 hour
    ------------------------
    - initial design of PWServer
    - check to be sure design meets requirements
    - made implementation notes to self
    

  3. 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
    

  4. 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.

  5. Turn in the project files using the hw2submit.sh submit script.

  6. 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.

Technical Requirements and Specifications

  1. The file pwserver.h must contain the definition of the class PWServer and pwserver.cpp must contain the implementation of the PWServer methods.

  2. The class PWServer should provide the following services through its public interface:

    bool CheckPW    (const char* uid, char* upw);
    // return 1 if <uid , upw> is authentic, 0 otherwise
    
    bool ChangePW   (const char* uid, char* upw, char* newpw);   
    // return 1 if user password is successfully updated, 0 otherwise
    
    bool CreateUser (const char* uid, char* upw);
    // return 1 if new user is successfully entered into system, 0 otherwise
    
    bool 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.)

  3. 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.

  4. The file structure assumed by PWServer objects is as follows.

    For example:
    7
    burns   24240
    gaitros 57423
    lacher  39884
    porter  9041
    rupert  18097
    tarokh  20692
    toh     63429
    

  5. Your solution is required to run with the supplied client program hw2/pwclient.cpp.

  6. A PWServer object should store its uid-signature data in a private fsu::TVector<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).

  7. 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.)

  8. 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.

Hints: