Project 1: Password Server

Due: 02/13/05

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

Procedural Requirements

  1. Work within your subdirectory called cop4530/proj1.

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

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

  4. Turn in the the project files using the proj1submit.sh submit script.

  5. 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:

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

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

  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 proj1/pwtest.cpp.

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

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

  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: