copyright | disclaimer | privacy | contact  
Australia's Leading Computer Emergency Response Team
 
Search this site

 
On this site

 > HOME
 > About AusCERT
 > Membership
 > Contact Us
 > PKI Services
 > Publications
 > Sec. Bulletins
 > Conferences
 > News & Media
 > Services
 > Web Log
 > Site Map
 > Site Help
 > Member login





 

Selected Aspects of Computer Security in Open Systems

Date: 08 November 1993

Click here for printable version

Danny Smith
Australian Computer Emergency Response Team
c/- Prentice Centre
The University of Queensland
Qld. 4072. Australia
+61 7 3365 4417
auscert@auscert.org.au
8 November, 1993

Abstract

The past five years have seen a lot more attention being focused on computer and network security. This heightened awareness has given rise to a better understanding of the types of threats a computer system might face. It has also seen the development of new tools and techniques to combat those threats.

This report examines those security threats, and details what tools and techniques are available to combat them. These threats are categorised using standard criteria. In addition, an examination of common authentication techniques is provided, including a comparison between X.509 Strong Authentication and Kerberos.

The suite of available tools is complemented, through the development of a one time password system for OpenVMS. This development is also detailed.

1. Introduction

In November 1988 a "worm" program was released to the Internet, which (although not entirely intended by the worm's author) brought much of the network to a standstill [Spa88]. Many sites disconnected from the network until a solution could be found. This had an added disadvantage as many of the first fixes for the problem were distributed via the network.

In March 1991 a ship was lost in the Bay of Biscay. A weather forecasting satellite was not working as the integrity of a computer system at the European Weather Forecasting Centre in Bracknell, Berkshire had been broken by intruders. The storm that caused the ship to sink had not been predicted [Aus93].

In mid-1993, a number of sensitive medical test results were changed from negative to positive by intruders. Various people around that country were then led to believe that they had cancer. It was up to Police to advise them that the results had been changed and were incorrect [Aus93].

Often when the topic of computer security is discussed, particularly when it relates to intruders gaining unauthorised access, a wide range of viewpoints can be expressed. Some people view intruders as "fighters for freedom of information"; others view them as "joyriders in the electronic space"; others view them as "intellectual joyriders". Mostly the intruders do not cause harm, but when intruders perform acts like the sample described above, it is difficult to tell the difference between the "good" intruder and the "bad" intruder. Therefore, these intruders must now be treated as criminals.

This report examines the topic of computer security for Open Systems. In this report, an Open System is one that is connected to a global network without any network access restrictions. There are many other more restrictive definitions for an Open System, but this one will suffice for the purposes of this report.

Over the past five years, computer vulnerabilities and the way they are exploited has changed character. In 1988, many intruders were simply exploiting poor password choices, poor system configuration, or software vulnerabilities [CER92]. However, in 1993, those types of attacks are still common, with the addition of people viewing network information, either through modifying the network software or looking at data that traverses the network links. Intruders are now also actively examining source code for operating systems and utilities in an attempt to determine any potential vulnerabilities, or attempting to exploit poor protocol design to try and fool servers into performing some action.

This report examines the common threats faced by computer systems, and looks at various tools and techniques that have been developed to combat these threats. Its aim is to examine what tools and techniques are available to combat the known threats. As a result of this examination, an extension is provided to the security on one open system (OpenVMS) where a deficiency is perceived.

Whilst this report concentrates heavily on the Unix operating system, many of the vulnerabilities and techniques to combat them apply to all operating systems. The approach taken is not a formal one, but a comparison of services provided from a user perspective. There are many papers that formalise the analysis of security (for example, see [LAB92]).

This report commences with an examination of authentication techniques, as authentication forms the backbone which other security services build upon. It is for this reason that the author believes that more attention should be devoted to it. This examination is found in Chapter 2.

Chapter 3 examines common threats faced by computer systems today. These threats involve more than authentication services, and special attention is devoted to categorising them based upon standard criteria. In particular, this chapter captures the wider spectrum of computer security, as some of the detailed threats bypass authentication, rendering them potentially more dangerous than they might first appear.

Chapter 4 contains an examination and evaluation of what tools are readily available to combat the common threats outlined. These tools have been developed to address the problems of computer security.

Once the general problems of security are detailed and solutions proposed, an extension is proposed to an existing system (OpenVMS) for which a common solution (one time passwords) to a standard problem (network monitoring) is not freely available. A design and implementation is supplied in Chapter 5 for the proposed extension. All source code and header files are found in Appendix A.

2. Authentication Techniques

This chapter reviews computer security services in broad terms, and indicates why authentication is important. It examines the threats that authentication faces, and then compares some of the commonly used authentication techniques in use, looking at their strengths and weaknesses. These techniques generally involve the use of a password to effect the authentication.

Some of the newer techniques use public and private key encryption techniques to protect and enhance the authentication sequence. These authentication techniques are also examined, looking at their strengths and weaknesses.

2.1. What is Security?

Businesses today require the processing and exchange of information to conduct their business dealings. The Twentieth Century has been described as "The Information Age". Security of that information is now paramount if business is to survive in this new world [CLS91], [Edw90].

In order to protect against perceived threats, various security services need to be provided. These can include [ISO92]:

Authentication
The act of identifying that an entity is who or what they are claiming to be;

Access Control
The act of deciding whether an entity has the rights or authority to perform the task they are seeking to do;

Data Confidentiality
The act of protecting data from unauthorised disclosure;

Data Integrity
The act of protecting data from manipulation;

Non-repudiation
The act of proving the integrity and origin of data in an unforgeable relationship which can be verified by a third party at any time.

When imposing access controls, judgment must be reliable and predictable, and the contents of an audit trail should be accurate to resolve any problems that might occur. Data Integrity and Confidentiality may be questioned if the identity of the entity being communicated with is in doubt [JM91]. Correct identification is impossible if the act of authenticating the entity is deficient, and doubts are cast on the integrity of the authentication.

2.2. What are the Threats to Data Security?

The act of authenticating an entity must be trustworthy if there is to be any security in the computer system at all. This means that the authentication facility must correctly identify the entity in all cases, regardless of the role played by the entity, the location of the entity, and the presence or absence of potential enemies. The commonly known threats [ISO92] to data security include identity interception, masquerade, reply, data interception, manipulation, repudiation, denial of service, misrouting, and traffic analysis. These threats are more fully defined in Section 3.1.

This remainder of this chapter examines a number of authentication schemes and indicates if any protection is provided for Identity Interception, Masquerade, Replay, and Repudiation threats.

One of the aspects of authentication that is often overlooked is that it is a two-way street. When a user authenticates to a computer, it is often just as important that the computer authenticates to the user. This has been highlighted in recent times with people starting "trojan horse" programs that imitate the login sequence, but in reality capture the account and password for use by an enemy. Another example is an automatic teller machine that captures account numbers and PINs for later use. The ability to authenticate the machine will help to prevent these types of attack.

2.3. How is Authentication Achieved?

Authentication is generally achieved through one or more of three items [CLS91]. These are:

Something you know. This could for example be a password or Personal Identification Number (PIN);

Something you possess. This could be an identity card, an access card for electronic locks, or a smart card;

Something you are. This involves something personal about the subject such as a retina scan. It may also involve the role played when seeking authentication. For example, a person's rights to perform an action are vastly different if they acting as a Lecturer in a Department, as opposed to acting as the Head of that Department. The role of Head may be delegated via authentication.

Most authentication techniques make use of one of these items (such as knowing a password, possessing a key to an office, or signing of a name as the Head of Department). Whilst in many circumstances this is sufficient, the authentication can be enhanced by combining two or more of these items together. Examples of this might be possessing an identity card indicating the status of Head of Department which must be shown to security personnel, plus possessing a key to that office. If an attempt to enter the office of Head of Department with an identity card that indicates the status of a lecturer is attempted, then the security personnel should forbid this.

Another example in common use is the possession of a magnetic stripe card from the bank, and knowing the PIN for that card.

The following sections investigate different authentication techniques, describing their attributes according to this criteria, and looks at how well they cope with the threats detailed above.

2.4. Passwords

2.4.1. Normal Passwords

This is by far the most common authentication technique used on computer systems today. It is categorised by "something you know" which is a user account and the password for that account. If this information is revealed in any way, then an enemy can authenticate to the computer system, pretending to be a legitimate user.

Passwords are stored and handled in many different ways by different computer systems.

2.4.1.1. Plain Text, Non-world Readable

International Business Machine's VM/CMS stores its passwords in the directory file in plain text (EBCDIC), but to access this file requires special privileges and permissions which are normally only granted to a small number of users [IBM89].

Strengths
This system is very easy to implement, easy to maintain, and easy to logically prove that it is behaving correctly (that is, it can be trusted).

Should a user forget their password, it is relatively easy for an authorised person to find out what the password was, and reveal this information to the user.

Weaknesses
If access is gained to the password file by an unauthorised person, then every account and password on the system is revealed. This means that the Virtual Machine concept that so effectively keeps users separated is compromised, and any and all data on the system is available to the enemy.

Since all passwords are stored in plain text, then security personnel for that system will have access to every password. This would include passwords for the Head of Department, leading researchers, system administrators, and general users. It would be relatively easy to access any information on the system, masquerading as the owner of that information, thus bypassing the access control and auditing facilities.

2.4.1.2. World Readable, but Encrypted

This is the technique mostly used on UNIX systems. The password is encrypted using a one way function (one that is computationally difficult to reverse), and the encrypted version stored in a world readable file. The encryption is further protected by "salting" the algorithm with a seed chosen from 4096 possible values. This means that a single password may produce up to 4096 possible encryptions [GS91].

Strengths
The algorithm to encrypt passwords operates by encrypting a null string several times over with an altered version of the DES [FIP77] algorithm using the plain text password as the key. To date, nobody has successfully reversed this algorithm in a reasonable time.

By having the password file world readable and the algorithm publicly available, it simplifies the design of applications that require authentication services. Therefore, it is simpler to prove that these applications are performing correctly (that is, they can be trusted).

The UNIX authentication system has been subject to much attention due to its public nature. It has been studied, targeted, tested, and analysed by many people. This should give a user confidence that the algorithm is computationally strong enough for the present moment (provided a good password is chosen - see [SER93]).

Weaknesses
The major weakness of this authentication technique is the combination of the world readable file, and human nature. Whilst the encryption algorithm has not been reversed, much success has been gained by guessing the plain text password, encrypting it, and checking the encrypted value against the encrypted password stored in the password file. These password guessing attacks are notoriously successful because human users often pick passwords that are easy to remember, which often means that they are easy to guess [CLS91].

Another weakness also involves brute force attacks. With the speed of computers ever increasing in leaps and bounds, it may be feasible one day to just try all possible passwords against the encrypted version. The current estimates for encryption speeds indicate that 6,400,000 passwords encryptions may be achieved per second. This enables all combinations of six character passwords to be tested in less than one hour [Mad93]. The ability to check all eight character (maximum length) passwords may not be all that far in the future. Therefore, it is imperative that the password be changed regularly, well chosen, and not reused.

2.4.1.3. Non-world Readable Passwords

One of the major problems with UNIX style passwords is that the encrypted password is freely available. Many attacks are performed by taking a copy of the encrypted password to another machine, and applying attacks such as dictionary attacks in an attempt to guess the password. Often the remote machine belongs to another site, and has been compromised previously. The crackers use the resources of one organisation to gain access to another organisation .

One mechanism to prevent the encrypted password from becoming known is to store it in a non-world readable file. This concept is called shadow passwords on UNIX systems. The utilities that require access to the encrypted password require special privileges to perform their tasks. The encrypted passwords can still be read by privileged users, but by protecting the shadow password file appropriately, then the normal system user cannot gain access to the information. This means that even if a password is guessed, and access gained to the system, the rest of the encrypted passwords are still secure.

Digital Equipment Corporation's OpenVMS has had the concept on non-readable authorisation files for many years now [DEC90]. Access can be gained to the authorisation file through the use of special system calls and utilities. Access to some parts of the authorisation file are freely available, whilst access to others is restricted to privileged users.

This still does not prevent password guessing attacks. However, since the password file is now not world readable, the password guessing attacks will need to be performed on the machine under attack. Both OpenVMS and UNIX implement mechanisms to prevent or limit the success of such attacks.

Ultimately, the success of the password mechanism is up to the individual user to ensure that their password is well chosen, and not easy to guess. Analysis of how to form a well chosen password can be found in many places. Some examples are [SER93], [Cur90], and [Bis92a].

Strengths
It now requires privileged access to copy the password file and take it away to another machine to run password guessing programs on it. This means that if one user allows access to the system via an easy to guess password, the rest of the passwords are not at risk by that user's actions.

Since the encrypted password is no longer freely available, password guessing attacks need to be performed on the local machine (by attempting to login to the account using different passwords). This allows the machine to take action against this type of attack, and enforce a consistent security policy.

Weaknesses
Now that it requires privileged code to access the encrypted password, it requires special care must be taken to ensure that extra security holes are not created. This means that it is becoming more difficult to prove that the security system is behaving as expected (that is, it can be trusted).

Such systems are sometimes not standard, as shipped with the original operating system code. Therefore, they are security "add-ons" which are provided by a third party. This means that they may not have received a thorough testing, or may be inconsistent with existing operating system code. Generally however, most of these types of security systems have been around long enough to have the major bugs fixed in them.

The password is still transmitted in plain text on the network, thus rendering this scheme susceptible to replay attacks.

2.4.2. Passwords - Secure?

Are passwords all that secure then? It is possible to protect them from being revealed, either while they are stored on the system or being transmitted as part of an authentication sequence.

Klein [Kle90] completed a study of password security by attempting to "guess" passwords that were in use on various machines. After 12 CPU months, more than 25% of the passwords were guessed. Of those passwords guessed, more than half of them were six characters or less. This, coupled with the huge advancements in computer speed almost brings a brute force attack into the realms of reality - no password will be safe [Alv90]!

Guessing just ONE password is enough to allow access to a system. Having achieved this, an intruder has taken the first step to gaining further access to the computer system [Che92]. Some suggest that a hit rate of over 50% is not uncommon the first time a password guessing program is run [KC90]. Of five documented security incidents at Columbia University, all were as a result of bad passwords [BKS90].

The problem with passwords is not the password itself, but the people choosing them [Sto89]. If all passwords were a MINIMUM of eight characters, and chosen well, then this type of attack would at best, yield minimal results [Spa92].

In the past, it was always considered poor practice to write a password down. Now instead of writing it down, it is simpler for a user to choose a password that is easy to remember (which most likely means it is also easy to guess). Maybe it is time to start rethinking the policies, and suggest that it is alright to write a password down, provided that the password is stored in a safe place. The literature is full of papers where passwords are guessed, but no article indicated that someone's wallet was stolen to retrieve the password. Even if it is, the owner will know that their password has been compromised, rather than relying on chance to discover that someone has guessed their password [Bra90].

The use of default passwords and poorly chosen passwords has caused many problems over the years [CER92]. This is the major flaw that allowed the WANK and OILZ worms to operate [LS93].

The choice of passwords can be improved by preventing users from changing their password to a known "bad" password. The checks for a bad password are often done at the time the user changes their password, using a form of pro-active password checker (Some examples are [Bis92a], [Kur90], and [Spa92]).

2.4.3. One Time Passwords

One-time password generators generally employ a combination of hardware and software to effect the authentication. This technique employs "something you possess" and in some systems, "something you know". The hardware devices are usually known as "tokens" or "password tokens". There are several techniques in use [Ell92], [CER92], [Lau92].

One system operates by issuing a challenge which consists of a seven digit number (in phone number format). The user enters their PIN number and the challenge number into the hand held device which returns a seven digit response code to reply with.

Other systems use a changing, non-reusable password system. Each time the user authenticates, a new password is supplied by the hand held device. There is no challenge-response system, and the user must keep in synchronisation with password usage to prevent a denial of service. Some of these systems can support single use password generation for up to eight separate host systems. Some of these systems require that a PIN be entered before the next password is issued.

Another system displays the password continuously, changing it every minute or so. The host must not only keep the user's key (for generating the same sequence), but also a synchronised clock. This system is vulnerable to replay attacks if the password is captured and replayed prior to it changing. Some systems combat this by only allowing each password to be used once.

Some of the systems that employ the use of a PIN allow a second PIN to be specified. This may be useful for example to alert security staff that the user is being held and forced to access the computer system against their will. The second PIN may allow the user access (in a restricted sense), but also signal alarms!

S/Key is a software only system that requires the user to carry a list of generated passwords with them [KHW93]. S/Key is discussed in Section 4.4.9 and Chapter 5.

Strengths
The one-time password system is extremely effective in preventing replay attacks, provided the enemy does not know the sequence of generated passwords (either by guessing, or possession of a similar device and key). Many attacks in the present time will be instigated by monitoring authentication sequences [CER92].

Extra techniques can be employed that allow limited access, whilst also generating an alarm to indicate that this authentication sequence is being forced by an enemy.

Weaknesses
One of the major disadvantages is that to authenticate, the user must carry the hardware or password list with them at all times. If they do not possess the hand held device, then authentication cannot take place.

Some of the systems have a requirement for synchronised clocks. This suffers the same problems that Kerberos does, with denial of service due to clocks slipping, or an attacker targeting the clock synchronisation protocols to set the time to any desired value.

One particular system used generated keys that were so short, that it was easier to guess the password "1111" than it was to guess the correct password for the generated key [Bra90].

Systems that make use of a password that changes at time intervals may be prone to replay attacks if special precautions are not taken to prevent them.

2.5. Kerberos

The following analysis is drawn from [Ste90], [BM91], and [KCS90].

The Kerberos authentication system was produced at MIT as a part of Project Athena. It is a system that uses protocols which allow authentication to take place, even under the assumption that the network is under the complete control of an enemy. Kerberos uses a private key cryptosystem to protect the information from disclosure and modification. The user interface is the same as that for normal passwords, so this authentication technique is categorised as "something you know".

Kerberos authenticates a principal by providing evidence of that principal's identity. The principal can be a user or a particular service on some machine. A principal consists of the tuple:

<primaryname, instance, realm>

If the principal is a user (a person), then the primary name is the login identifier, and the instance is either null or represents particular attributes of the user. For a service, the service name is used as the primary name and the machine name is used as the instance. The realm is used to distinguish among different authentication domains. It is therefore possible to have several Kerberos databases serving an organisation rather than one large and universally trusted one.

In the following discussion, the symbols used are defined as:

c
client principal

s
server principal

tgs
ticket granting server

Kx
private key of "x"

Kc,s
session key for "c" and "s"

{ info}Kx
"info" encrypted in key Kx

{ Tc,s}Ks
Encrypted ticket for "c" to use "s"

{ Ac}Kc,s
Encrypted authenticator for "c" to use "s"

addr
client's IP address

Kerberos principals may obtain tickets for services from a special server known as the ticket-granting server or TGS. A ticket contains assorted information identifying the principal, encrypted using the private key of the service. timestamp1 indicates the time the ticket was issued, and lifetime the amount of time the ticket is valid for.

{ Tc,s}Ks = { s, c, addr, timestamp1, lifetime, Kc,s}Ks

Since only Kerberos and the service share the private key Ks, the ticket is known to be authentic. The ticket contains a new private session key, Kc,s, known to the client as well (when it obtains the ticket); this key may be used to encrypt transactions during the session which protects them from disclosure and modification.

To guard against replay attacks, all tickets presented are accompanied by an authenticator:

{ Ac}Kc,s = { c, addr, timestamp2}Kc,s

This is a string encrypted in the session key and containing timestamp2 (the current time); if the time does not match the current time within the (predetermined) clock skew limits, the request is assumed to be fraudulent. This clock skew limit is usually in the order of five minutes.

For services where the client needs bidirectional authentication, the server can reply with:

{ timestamp2 + 1}Kc,s

This demonstrates that the server was able to read timestamp from the authenticator, and hence that it knew Kc,s; that in turn is only available in the ticket, which is encrypted using the server's private key.

Tickets are obtained from the TGS by sending a request:

s, { Tc,tgs}Ktgs, { Ac}Kc,tgs

In other words, an ordinary ticket/authenticator pair is used; the ticket is known as the ticket-granting ticket. The TGS responds with a ticket for server s and a copy of Kc,s, all encrypted with a private key shared by the TGS and the principal:

{ { Tc,s}Ks, Kc,s}Kc,tgs

The session key Kc,s is a newly-chosen random key.

The key Kc,tgs and the ticket-granting ticket itself, are obtained at session-start time. The client sends a message to Kerberos with a principal name; Kerberos responds with:

{ Kc,tgs, { Tc,tgs}Ktgs}Kc

The client key Kc is derived from a non-invertible transform of the user's typed password. Thus all privileges depend ultimately on this one key (the user's password).

Note that servers must possess private keys of their own, in order to decrypt tickets. These keys are stored in a secure location on the server's machine. The security of the tickets and ultimately, the session key depends upon the security of the server private keys.

Strengths
The major strength of Kerberos is that the password is never transmitted on the network in plain text. This reduces the likelihood of the password being captured and replayed.

The tickets and authenticators include a timestamp which goes a long way to preventing replay attacks. If an authenticator is captured and replayed, it will not be valid the second time around provided sufficient time has passed by. To use this type of attack successfully requires either breaking the encryption algorithm, or possessing the password, the session key, or the server key.

This style of authentication was designed with the distributed or networking environment in mind. It is well suited to the client-server model often used in networking applications.

Since both the ticket and authenticator contain the network address of the client, another workstation cannot use stolen copies without changing their network address to be the same as the one that owns the stolen copy.

Weaknesses
The timestamps are critical to the successful operation of Kerberos. The times on the source and target machines must be closely aligned, or it will be possible that a valid ticket will be rejected as fraudulent because the time is incorrect. Typically, a clock drift of five minutes will cause a denial of service.

Relying on the time to be in synchronisation means that one should also protect the protocols that set the time, so that an enemy cannot adjust the time to their will via this mechanism.

Tickets are reusable. However, after having been authenticated for a long period (typically eight hours), it is necessary to generate a new ticket. This is because every ticket has a lifetime so once it expires, a new ticket must be generated by entering the login name and password to Kerberos again.

Within the Project Athena environment (and hence, Kerberos), the primary need is for user to server authentication. That is, when a user sits down at a workstation, that person needs access to private files residing on a server. The workstation itself has no such files, and hence has no need to contact the server or even identify itself. This is in marked contrast to a typical UNIX system's view of the world. Such systems do have an identity, and they do own files. Assorted network daemons transfer files in the background, clock daemons perform management functions, electronic mail and news is transferred, and so on. If such a machine relied on servers to store its files, it would have to prove its identity when talking to these servers. This means that Kerberos is designed to authenticate the end-user - a human being sitting at a keyboard. It is not a peer to peer system, intended to be used by one computer's daemons when contacting another computer.

In a workstation environment, it is quite simple for an intruder to replace the login command with a version that contains a trojan horse (captures accounts and passwords). Such an attack negates the primary strength of Kerberos, that passwords are not transmitted in plain text over a network. While this problem is not restricted to Kerberos environments, the Kerberos protocol makes it difficult to employ the standard countermeasure: one-time passwords.

The authenticator relies on the use of a timestamp to prevent against replay. Given that the lifetime of an authenticator is typically five minutes, a window of opportunity exists where a stolen live authenticator could be used to fraudulently gain access to a server. It has been suggested that the proper defence is for the server to store all live authenticators so that a replay could be detected. However, on UNIX systems, TCP-based servers generally operate by forking a separate process to handle each incoming request. Since the child and the parent do not share any memory, it is not convenient to communicate to the parent (or any other child processes) the value of any authenticator that is presented. UDP-based query servers generally use a single process to handle all incoming requests, but may have problems with legitimate retransmissions of the client's request if the answer gets lost.

Whilst the Kerberos system guards against having to send the password in plain text, the passwords chosen are much the same as the standard UNIX password, and therefore suffer the same fate of normal passwords in a password guessing attack. At least the encrypted password is not freely available (it is stored on the Kerberos server), so the success of such an attack is much the same as the system where the passwords are not world readable. To succeed with such an attack would require the password to be guessed, rather than just replaying the authentication sequence again. This would allow an enemy to masquerade as another user, without using replay.

Tickets are based upon a system's IP address. On multi-homed systems (systems with more than one network interface and IP address), this may cause a problem as the ticket will only be valid through one of those interfaces.

Tickets are stored in /tmp which does not work very well for multi-user systems.

2.6. X.509 Authentication

X.509 is a CCITT Recommendation (as well as being Part 8 of ISO International Standard 9594) [ISO92]. This standard forms the Authentication Framework, initially for use by the X.500 Directory Service. The concepts and facilities provided by this framework could be used by other OSI applications, or by network applications in general that require such facilities.

The strong authentication techniques of X.509 make use of public key cryptosystems (PKCS), unlike Kerberos. The recommendation states the requirements of the PKCS, without dictating any particular algorithm. It also includes the mechanisms for specifying the algorithm in use, and therefore no single algorithm is required to successfully use X.509 services. One particular algorithm that is commonly known, and satisfies the criteria dictated by X.509 is RSA [RSA78].

Whilst X.509 could be categorised by "something you know" (the password), it is possible that the secret key for the PKCS (when using strong authentication) could be so complex that it is necessary to store it on something like a "smart card". This system might require a smaller password to unlock the smart card. In this case, the authentication is categorised by "something you possess" and "something you know".

The recommendation describes more facilities than just authentication (such as Certificates, Certification Authorities, and Digital Signatures), but the scope of this report is limited to the use of digital signatures and authentication facilities. The authentication facilities range from very simple authentication, to extremely strong mechanisms.

2.6.1. Digital Signatures

Information can be signed by appending to it an encrypted summary of the information. The summary is produced by means of a one-way hash function while the encrypting is done by applying a public key cryptosystem and using the secret key of the signer. This can be shown as [ISO92]:

X{Information} = Information, Xs[h( Information)]

The signature can be verified by applying the one-way hash function to the information, and comparing the result with that obtained by decrypting the signature using the public key of the signer [Law93].

2.6.2. Simple Authentication

Simple Authentication is intended to provide local authorisation based upon the name of a user (their username) and an agreed password. Utilisation of Simple Authentication is primarily intended for local use only, and will not be very effective in a wider scope. There are two means by which Simple Authentication can be achieved:

2.6.2.1. Unprotected Simple Authentication

The procedure for achieving unprotected simple authentication is:

1. an originating user A sends their username and password to a recipient user B (the target machine);

2. B consults a local database of usernames and passwords where the password is checked against that held for user A;

3. the success or failure of authentication may be conveyed to A.

Unprotected Simple Authentication involves the transfer of the username and password in clear text to the recipient for evaluation. It is therefore prone to attack from network traffic monitors. As well, the passwords are stored in plain text, and may be discovered if privileged access to the database is gained.

Unprotected Simple Authentication could be compared with the plain text, non-world readable password mechanisms currently in use, such as that used in VM/CMS. If the password is stored in the database in encrypted form, then this mechanism would be comparable to the authentication systems found in Operating Systems such as UNIX and OpenVMS.

2.6.2.2. Protected Simple Authentication

The procedure for achieving protected simple authentication is:

1. an originating user A sends their protected identifying information (Authenticator1) to a recipient user B (the target machine).

Authenticator1 = timestamp1, random1, username, Protected1
Protected1 = ƒ1( timestamp1, random1, username, password)

where random1 is a random number with an optional counter included (where used), timestamp1 consists of timestamps (where used), username is the login identifier for the user seeking authentication, and Protected1 is timestamp1, random1, username, and password, all processed through the one-way function ƒ1;

2. B verifies the protected identifying information offered by A by generating (using the information included in Authenticator1, plus a local copy of the password for A) a local copy of Protected1. B compares for equality the locally generated copy of Protected1 with the one supplied by A;

3. B confirms or denies the identity of A based upon this verification.

The procedure can be further protected by passing Protected1 through another one-way function ƒ2, and including a second random number and/or timestamp.

Authenticator2 = timestamp1, timestamp2, random1, random2, Protected2.
Protected2 = ƒ2( timestamp2, random2, Protected1)

There is no requirement for ƒ1 and ƒ2 to be the same, or different. The principle for verifying the identity of A is the same as before.

The use of random numbers and timestamps is to minimise the chance of a successful replay attack, and to conceal the password from a cryptographic attack.

Protected Simple Authentication provides similar facilities to Kerberos in that it prevents the password from being transmitted in plain text, and provides some protection against replay attacks. However, Protected Simple Authentication does not provide bidirectional authentication which Kerberos can.

Strengths
One of the strengths of this technique is that it is relatively easy to implement, and to prove that it is functioning correctly without vulnerabilities. The more complex the system, the harder it is to determine the lack of vulnerabilities.

Another advantage is that it is quick to determine if the authentication attempt is successful or not. For transaction style operations, the time to authenticate a user may become an important issue.

The use of timestamps and random numbers reduces the chance of a successful replay attack.

Weaknesses
Since the username and password are transmitted in plain text, simple authentication can suffer from network monitoring and replay attacks.

The password is stored in the password database in plain text, so it is relying on the database security to protect the password from being revealed.

2.6.3. Strong Authentication

Strong Authentication is defined as "Authentication by means of cryptographically derived credentials" [ISO92]. The approach taken by X.509 makes use of public-key cryptosystems. These systems use two keys, one secret and one public, rather than a single key in private key cryptosystems.

Each user is identified by their possession of the secret key. A second user or system is able to determine if a partner is in possession of the secret key without either learning that secret key, nor revealing their own secret key to anyone.

Strong Authentication can be 1-way, 2-way, or 3-way as described below.

2.6.3.1. One-way Strong Authentication

One-way authentication is used when user A wishes to authenticate to user B (a system or user). A sends the following message:

B Æ A, A{ tA, rA, B}

where B Æ A is a chain of certificates (certification path from B to A) which ultimately verifies the public key for A, tA is a timestamp consisting of one or two dates (generation time and expiry time), rA is a non-repeating number, and A{ tA, rA, B} is the authentication information digitally signed by user A (by appending an encrypted summary to the end). The timestamps are used to determine the life of the token, and help to detect replay attacks. rA is valid until the expiry date indicated in tA.

It is possible to include extra information such as sgnData which will be used to provide data origin authentication by digital signatures, and encData which will be used as a secret key for the cryptosystem specified in sgnData. encData is encrypted using the public key of B, so that only B can read it.

B Æ A, A{ tA, rA, B, sgnData, Bp[ encData]}

Note: This last form of message (authentication token) in certain circumstances is subject to a form of manipulation attack. For example, suppose a token accompanies a confidential message which is encrypted with a key that is contained in encData. An opponent C might intercept the transmission of the token plus encrypted message from A to B and substitute a transmission consisting of the token:

C, C{ tC, rC, B, sgnData, Bp[ encData]}

plus the original encrypted message. B, upon receipt of this substitute transmission might, in its response to C, disclose confidential information from the original encrypted message (which would have otherwise be inaccessible to C). One method that can avert such a threat is to include additional information in the encrypted message that accompanies the token (e.g., A, tA, and rA).

2.6.3.2. Two-way Strong Authentication

Two-way authentication is used when user A wishes to authenticate to user B (a system or user), and wishes B to authenticate to A in return. The initial authentication sequence is the same as one-way strong authentication, but B then returns to A:

B{ tB, rB, A, rA}

where tB is a timestamp defined in the same way as tA, rB is a non-repeating number used for similar purposes to rA, and B{ tB, rB, A, rA} is the authentication information digitally signed using B's secret key.

It is possible to include extra information such as sgnData which will be used to provide data origin authentication by digital signatures, and encData which will be used as a secret key for the cryptosystem specified in sgnData. encData is encrypted using the public key of A, so that only A can read it.

B{ tB, rB, A, rA, sgnData, Ap[ encData]}

Note that this means that separate keys may be used to provide data confidentiality in each direction of data transfer.

2.6.3.3. Three-way Strong Authentication

Three-way authentication is used to establish the same properties as two-way authentication, but does so without the need for association timestamp checking. It involves a further transfer from A to B.

The first two exchanges between A and B are the same for two-way authentication except that the timestamps may be zero, and they need not be checked. A then sends to B:

A{ rB, B}

B needs to check that the received rB is the same as the one sent. A{ rB, B} is the authentication information digitally signed using the secret key of A.

Strengths

One of the strengths of X.509 is that it is not hampered by existing systems in its operation and interface. It has the ability to use any facility at its disposal to perform its function well. However, this is a double-edged sword in that by trying to specify a system that is usable by all, it may be that the system is not usable by anybody. This may be reflected by the lack of widespread deployment of this system.

X.509 protects the authentication sequence from replay through the use of timestamps and random numbers. The inclusion of the random number means that it is not required that the token be valid for any longer than necessary. The authentication instance is defined by a combination of timestamp and random number, which will be unique in a defined time period. All that is required is that the entity performing the authentication cache the random numbers for at least the period of validity of the timestamp.

X.509 protects the authentication sequence from disclosure through the use of public key encryption. This allows a third party to verify the correct credentials of a purported entity, without that entity ever having to exchange any secret information, or revealing that entity's secret key. Thus authentication can be undertaken between mutually distrusting parties, provided a trusted entity verifies that the public keys of those parties are correct.

X.509 does not mandate the use of any particular public key encryption algorithm. One algorithm that matches the requirements of X.509 is RSA [RSA78]. This algorithm has to date been difficult to break, provided that keys of a suitable size are chosen. One advantage of the RSA algorithm is that should a certain key size be determined to be insufficient, it is possible to increase the key size to attain the desired level of security. This would also have the effect of slowing the encryption down, and increasing complexity.

Weaknesses

The only public key encryption algorithm that has been currently used with X.509 is RSA [RSA78]. This algorithm uses keys that can be difficult to manage. Private key encryption can use keys that are only 56 bits long, and therefore, can be easy to remember ASCII text. RSA works on large prime numbers, which can be 100 digits long! Some people can be hard pressed remembering seven digit phone numbers!

The X.509 standard details some other potential threats that are specific to the strong authentication method [ISO92]. These are:

Compromise of the user's secret key: The identity of an entity is determined by possession of the secret key. Compromise of that key would mean that a rogue user could authenticate themself fraudulently;

Compromise of the Certification Authority's (CA) secret key: Certification Authorities are a trusted source that can be consulted to determine a user's public key. This key is transmitted inside a certificate (signed using the CA's secret key). If an invalid certificate is issued, than a rogue user could use an invalid secret/public key pair to authenticate as another user, and present what appears to be a valid certificate verifying the incorrect public key thus tricking an entity into believing that the invalid secret key in possession is the real one;

Misleading a CA into producing an invalid certificate: In general, CAs are kept off-line to the main processing, and this offers some protection. It is up to the CA to verify the credentials of a user before issuing them a certificate that verifies their public key;

Collusion between a rogue CA and user: This represents a basic breakdown of the trust relationship required in the X.509 method. There must be at least one entity that can be trusted to verify the credentials of all other (delegated) authorities. There are techniques to combat this type of threat. The reader is referred to [HY92] and [Smi93].

Forging of a certificate: As the certificate is signed by the CA, the only way a certificate can be forged is if the CA's secret key is compromised, or the encryption technique is broken;

Forging of a token: As the token is signed by the sender, the only way a token can be forged is if the sender's secret key is compromised, or the encryption technique is broken;

Replay of a token: This is protected against by using random numbers and timestamps;

Attack on the cryptographic system: "the likelihood of effective cryptanalysis of the system, based on advances in computational number theory and leading to the need for a greater key length are reasonably predictable." [ISO92].

Lack of practical experience with X.509 means that it is difficult to prove that it does provide the services it promises effectively and efficiently. A system such as Kerberos has been in use in real situations for many years now, and its deficiencies are relatively well known. X.509 may contain hidden weaknesses that are yet to surface.

2.7. Authentication Techniques versus Threats

This section details which of the authentication techniques above provide protection against a subset of the threats outlined in Section 3.1. The threats chosen from the detailed list are Identity Interception, Replay, Masquerade, and Repudiation. Data Interception and Manipulation are viewed as extensions of the previous list, and are not considered. Denial of Service, Misrouting, and Traffic Analysis form a special type of threat which generally only applies to systems with extreme security (such as Government, or Military).

Table 1 shows which authentication scheme provides protection for a subset of defined security threats.

Table 1 - Threats handled by authentication schemes
Identity Interception
Replay
Masquerade
Repudiation
plaintext passwords -
world readable
x
x
x
x
encrypted passwords -
world readable
x
x
x
x
encrypted passwords -
read protected
x
x
x
x
one time passwords
x
O
O
x
Kerberos
x
O
O
x
X.509
simple unprotected
x
x
x
x
X.509
simple protected
x
x
x
x
X.509
strong - 1-way
O
O
O
O (see Note 1)
X.509
strong - 2-way
O
O
O
O (see Note 1)
X.509
strong - 3-way
O
O
O
O (see Note 1)

Note 1: Non-repudiation is not provided by the authentication facilities of X.509 alone, but is, when coupled with other facilities such as Digital Signatures. Public key cryptosystems (under certain circumstances) can help to provide non-repudiation [Tan89] (as opposed to private key). By having a user sign data using their secret key, data integrity and data origin services are provided. This coupled with ensuring that only the specified user can view the data (by encrypting it using that user's public key - only they can decrypt it with their secret key) and having them reply using some part of that message signed by them, provides two way non-repudiation. The sender cannot claim not to have sent it (as only they could have signed it), and the receiver cannot claim that it was corrupted (it was signed) nor that they didn't receive it (as they replied using the contents of the message).

2.8. Comparison of Kerberos and X.509 Strong Authentication

X.509's one-way and two-way strong authentication techniques provide basically the same services that Kerberos provides, but uses different techniques. The basic differences between the two systems are shown in Table 2.

Table 2 - Comparison between Kerberos and X.509
Kerberos
X.509 Strong Authentication
Uses private key encryptionUses public key encryption
Requires keys to be exchanged prior to authentication Requires a trusted third party to verify the public key of the entity seeking authentication
Private key encryption relatively "easy" and "fast" to use Public key encryption relatively "difficult" or "expensive" to use
Key management more difficultKey management easier
Requires synchronised clocksThree-way strong authentication does not require synchronised clocks (one-way and two-way still require synchronised clocks)
Uses clocks to prevent replay attacks - a window of opportunity exists where an attack may be successful - can be reduced if the server caches previously used information Uses clocks and random numbers to prevent replay attacks - does not provide a window of opportunity provided the server caches previously used information
Is in current use in a variety of situations Currently not many implementations
Required to fit into an existing framework of operation - the interface is determined by pre-existing systems Provides services that clients must determine how best to use - no preconceptions on the interface

A number of development implementations and projects using concepts from X.509 have been undertaken over the last few years, but these systems are currently not in wide use. The reader is referred to [UCL92], [OSI92] and [TAP90] for further information.

3. Security Threats in Existing Operating Systems

This section describes the types of threats that a computer system may face. Note that it simply describes the class of attack, but does not detail how to actively exploit particular examples of this type of attack. This is deliberately done in the interests of those systems that may not be fully secure.

3.1. Classifying Threats

The X.509 standard details a number of threats that computer networks may face, particularly relating to authentication exchanges. These are detailed below [ISO92].

Identity Interception - this is the threat that the identity of one or more of the users involved in some form of communication is observed for later misuse. Generally, this is not a serious problem unless the nature of the parties communicating is in the political arena (for example, the leader of a rival company is observed to be communicating with one of the top employees in another company).

Masquerade - this is the threat where one user pretends to be another user. This usually takes place during an authentication sequence. Once the user has convinced the remote person or computer that they possess a different identity, the user then inherits all the access rights of the masqueraded user. When that masqueraded user is privileged, then this type of attack can be devastating.

Replay - this is a very simple form of attack where some sequence of events or commands is observed, and then replayed in the attempt to trick the user or machine into performing some act. This is mostly seen during authentication, by replaying another user's username and password, thus ultimately performing a masquerade attack. This is a special form of masquerade attack as it is the most common form in use; hence it is in a category of its own.

Data Interception - this type of threat can be observed at any time during network communications. The threat here is that some form of user data may be observed by another user that is unauthorised to see that data. This has many implications for confidentiality, or maintaining a secret during an basic authentication sequence.

Manipulation - if data can be manipulated either during transmission, or whilst it is stored on disk, then the potential for destructive action is enormous. In this case the integrity of all the data (not just that portion which was changed) is brought into question.

Repudiation - this is the denial of one or many of the users in having participated in a communication. This is more a problem in the business world where directives or agreements need the ability to be proven to have taken place.

Denial of Service - A denial of service attack is insidious as it often conflicts with other forms of security protection mechanisms. Denial of service can be categorised as the prevention or interruption of access to a service, or the delay of time-critical operations.

Misrouting - this is where a communication intended for one person is rerouted to another person.

Traffic Analysis - this is when the communication between two parties is observed, and information about that communication is obtained (such as absence/presence, frequency, direction, sequence, type, amount, and so on). This might be of importance for example to the military or government, particularly in an international arena.

The next section examines different types of attacks that have occurred in the past, and details how they they may be categorised using the above criteria.

3.2. Characteristics of Common Threats

3.2.1. Morris Worm

"On the evening of 2nd November 1988, a program was executed on one or more hosts connected to the Internet. This program collected host, network, and user information, then broke into other machines using vulnerabilities present in existing software. After breaking in, the program would replicate itself, and the replica would also attempt to infect other machines. The program spread quickly, and the scope of the breakins came as a great surprise to almost everyone." [Spa88].

The "worm" exposed some security flaws in standard system services. These flaws are now patched, with the patches widely available. The first flaw was a bug in the fingerd daemon. This program made a call to the gets() routine. gets() makes no checks on the bounds of the buffer involved, and hence it is possible to overflow that buffer (scanf(), sscanf(), fscanf(), and sprintf() also have this problem). The particular problem in question allowed the input buffer to be overflowed thus by careful selection of the input data, a privileged login shell could be obtained and any commands executed.

A simple change to prevent unbounded input was all that was required. This particular vulnerability is a typical example of poor coding practices. When a program is to be executed by a privileged or an unauthenticated user, extreme care must be taken to minimise any damage as a result of error conditions or extraneous events. In this case, the buffer overflow was not detected and a security risk was the result.

The second vulnerability exploited by the Morris Worm was in sendmail. sendmail is a mailer designed to route mail in a heterogeneous network. It is extremely large and complex. Configuring the sendmail system is a long and difficult task. Therefore, the sendmail developers allowed a DEBUG feature to be turned on to aid with configuring the system. Unfortunately, it was such a useful feature that it was often left turned on by default. When DEBUG was enabled, it allowed a user to issue a set of commands instead of the user's address as the recipient of a message. Since sendmail was running as a privileged program, this allowed those commands to operate in a privileged environment.

This is a classic example of poor configuration, particularly relating to extremely complex computer subsystems. Despite this being a well documented problem, automated attacks targeting this vulnerability are still being seen today, suggesting that perhaps some limited success is still being found using this mechanism [CER93].

The key attack of the worm involved attempts to discover user passwords. Since this is such a common problem, it is addressed as a separate issue in a later section.

Simple precautions would have prevented the rapid and widespread deployment of the Morris Worm. However in 1988, computer security did not have the high profile that it has today.

The Morris Worm was aimed at Unix based systems, specifically those based around the BSD variants. The next section shows that this type of attack is not a Unix-only problem.

3.2.2. WANK and OILZ Worms

The following analysis is drawn from [LS93].

During October and November of 1989, a different work attacked the SPAN and HEPnet networks, targeting VMS systems linked via DECnet. Whenever a system was infected with WANK or OILZ, the damage ranged from annoying users with messages and practical jokes to more major damage in terms of Trojan Horses and penetrated system accounts. After about two weeks, the attacks by WANK subsided. However, a new version of this worm, OILZ, attacked hundreds of additional systems. OILZ fixed a number of errors and problems that were present in WANK.

The WANK worm would use the network to examine remote systems with a view of learning what some usernames were on those systems. It would then attempt to guess a password for those accounts, initially only trying a small subset of passwords to help prevent detection. The worm would determine if it had penetrated a privileged account and further invade the system if possible.

The OILZ worm used the same techniques (correcting a few errors in the WANK worm), but also used the DECnet default account for penetration. This allowed the worm to spread to hundreds of additional systems.

Once the worm had penetrated the system using a privileged account, it would install back doors back into the system by editing any command file it could find to modify the password and privileges of a set account each time that command file was executed, it would disable mail to the system account (probably to prevent any notification of eradication scripts or other information), and it would modify user accounts to play a practical joke (which resulted in users deleting their own data).

These worms exploited standard attacks using already known vulnerabilities. The attacks that allowed the worms to propagate were the result of poor password choices, and default user accounts. Once again, it highlights that this is an extremely important security mechanism.

Similar worms have existed in the past which also exploited poor password choices and vendor supplied default passwords (such as the Father Christmas worm in 1988 [Cly93]).

A formal definition of worms can be found in [Coh92].

3.2.3. Trusted Systems

The discussion on this type of attack relates specifically around Unix, although it could easily apply to other Operating Systems (such as OpenVMS using the Network Proxy facility [Cly93] and DECnet [DEC88a]). Using a trusted system attack, many systems are compromised by using a trusted system on their local network that has itself been compromised [Cur90]. Turning this around, it means that once one system is compromised at a site, it is usually very easy to compromise many further systems at that site. There are a number of facilities that enable this to happen. The first is the file /etc/hosts.equiv. This file contains a list of all known hosts that this system trusts. In the past, many versions of the operating system were shipped by default as trusting all hosts on the global network. This was usually done as many of the workstations were being installed on closed networks, and not connected to the global network. It resulted in a system that was easy to install and get running in the local network. In the global network, this type of assumption is entirely inappropriate.

Another aspect of trusted systems attacks involves the use of the .rhosts file. This file works much the same as the hosts.equiv file, but only acts for specific users, rather than all users. The unfortunate part of this situation is that the file is entirely under the control of the user. This means that any individual user may lower the security of a system by allowing access to their account via any other system on the network.

The .rhosts file is used to grant access via the "r" commands (rlogin, rcp, rsh, rexec, rdist, and so on) [Sun90b]. However there are further vulnerabilities concerning these commands that have been discovered and rectified in the past. It was possible that the protocol exchange could be exploited to gain unauthorised access. This has the problem that the detection of this situation is usually out of the hands of the system administrator, and the system administrator now relies on third parties (such as an Incident Response Team) to detect and take responsibility for organising a software fix, and then disseminating the information to the sites.

The .rhosts file is used to identify the trusted system, by name. When all the host name to host address mappings were stored on the local system (for example, in the /etc/hosts file), this worked reasonably well. Now that these mappings are being performed by name servers that are usually out of the control of the local system administrator, it is possible to change the address to name binding to fool a system into thinking that the connection is coming from a trusted host [Ven92]. This represents a fundamental flaw in the design of these "r" commands.

The final style of vulnerability that falls under a trusted systems attack comes from inappropriate setting of the "secure" attribute on terminals [CER92]. This allows anyone using that terminal to login as "root" (the system privileged account on Unix). If the terminal is not set to secure, then the user must login on their own account first, and then change their identity to root, thus passing through more security checks and audit trails. Some operating systems by default had all of their terminals set to secure as it helped the system administrators when initially setting up their systems. Once again, this highlights the need for sensible default configurations when installing new software.

3.2.4. tftp

tftp stands for trivial file transfer protocol [Sun90b], [RFC783]. This works much the same as the normal file transfer protocol, but the word trivial is used to indicate that a number of key services are not implemented (such as user authentication!). tftp is mainly used by diskless workstations or smart terminals for boot information, configuration loading, or font loading. The major problem with tftp is that if care is not taken, it is possible to configure it in an inappropriate manner, thus allowing any world readable file on the system to be copied (generally, an intruder cannot overwrite a file, but even this is still possible under extreme circumstances). A typical tftp attack involves an intruder taking a copy of the /etc/passwd file to a remote site, and then attempting to guess the passwords. It is possible to automate these attacks, resulting in some widescale attempts at accessing password files (one single incident covered 65,000 individual hosts!) [CER92].

3.2.5. NIS

Sun Microsystem's Network Information Service (NIS) is a distributed database system that lets many computers share password files, group files, host tables, and other files over the network [Sun90a]. The files are stored centrally on a NIS server, and are made available to the NIS clients. The files on the clients contain a special entry (that begins with a "+") to specify that the NIS server needs to be contacted for the rest of the information. This can have some unexpected effects. For example, the /etc/passwd file has an entry that looks like:

+::0:0:::

On some older versions of NIS, it was possible that this line would be treated as a normal password file line if the NIS server was unavailable [GS91], [Sho93]. Therefore, logging in with an account of "+" resulted in a privileged login without requiring a password [Spa93]. Another danger is that should the "+" ever be accidentally removed or changed, the line becomes a valid password file entry, allowing a privileged login without a password.

Other problems include the ability for any user outside of the organisation to obtain copies of the database files exported by the NIS server, by guessing the name of the NIS domain, binding to it using the ypset command, and requesting the databases [Mor90]. One major vulnerability here is that the password file may be disclosed, allowing password crackers to be exploited.

There are design flaws in the code of some NIS implementations that allow a user to reconfigure and fool the NIS system [GS91]. One of the major ways that this is performed is to establish a ypserv program that acts like a real ypserv program and responds to ypbind requests. The ypbind daemon can then be instructed to use that program rather than the real ypserv daemon. In this way, an attacker can supply their own version of the password file (containing any information they think is appropriate!).

Another form of attack is performed by analysing the NIS data packets as they pass on the network, and providing a valid response to NIS requests [Mor90]. This requires detailed knowledge of the NIS protocol. There are techniques around that ensure that the reply is received by the client before the valid reply. Since it is a datagram protocol, once the client receives a reply, it ceases listening, and the second reply is lost. This allows an attacker to enter a computer system by supplying any username (the client doesn't know it isn't a valid username), and that user can then become privileged (despite the fact that the terminals have been set secure, or that only a subset of people are allowed to execute the su command!).

Many implementations of NIS have been tidied up, and a lot of these problems have been addressed. The final problem relating to NIS is a simple configuration error with the password files. The "+" entry in the /etc/passwd file indicates that NIS is running and should be consulted for further passwords. A common mistake is to also include a "+" line at the end of the password file that is being exported by the NIS server. This has the effect of validating the username "+" as a real user account [GS91].

3.2.6. NFS

The Network File System (NFS) allows different computers to share files over the network [Sun90a], [RFC1094]. NFS works using a client-sever model. Using NFS, clients can mount the disks on the server as if they were physically connected to themselves. Unlike other remote filesystems, NFS allows client users to read and change the contents of files stored on the server without ever having to log into the server or supply a server password. This characteristic is at the heart of NFS's security problems.

NFS can be made secure, however it is easy to incorrectly configure the system, often without even knowing. Most forms of NFS attacks take place on systems that are incorrectly configured. NFS unfortunately has a few "silent" features that may result in allowing any client on the network to mount the disk, if the configuration is not 100% correct. Inappropriate configuration allows any client to modify files on the server's disk, possibly installing malicious programs. The showmount -e command can be used to check a systems configuration to verify that what is set is appropriate.

Another security problem with NFS concerns file handles used to identify files [GS91]. These file handles consist of a filesystem id and an inode number, so it is relatively easy to guess valid file handles. The standard NFS server is unable to distinguish between falsified file handles from file handles that were created by the mount daemon. Therefore, the files on any NFS server are potentially accessible over the network by anyone who has the ability and determination to search for valid file handles.

3.2.7. Passwords

By far, the major strength (and weakness) in any system is the first line of defence - the authentication into the system. Any facility that allows someone access has the potential to allow anyone access. It is for this reason alone that special attention must be given to the authentication services. Unfortunately, this is not universally performed, and time after time, computer systems are being compromised as a result of a weak authentication service; in most cases a poor choice of password [CER92], [BKS90]. Sometimes the passwords are simply guessed, and with a small amount of knowledge it is usually easy to guess an inexperienced person's password. Mostly however, the password file is captured through some other means (perhaps tftp, or it may even be a local user with read access to that password file), taken to a remote site (often one that has been compromised!), and a variety of password guessing programs (password crackers) is run against it. It is a common belief that a young system intruder will never have sophisticated hardware to run password crackers on a password file. It is known that often the first thing the intruder does when compromising a system is to run a password cracker as a benchmark. If they decide the system is of sufficient speed, they will be back with a captured password file for cracking, possibly during the night hours only.

Many systems support the concept of a "guest" account, generally to make the system administrator's life a little easier when they are required to generate a temporary account for someone who is temporarily visiting. Unfortunately, the passwords chosen for these guest accounts tend to be extremely poor, more so than normal user accounts. It is not uncommon for the password of a "guest" account to be "guest". This represents a major flaw in the system security as it not only allows access to the computer system, but also prevents any valid audit trails, as it can be easily argued that anyone could be using that account when it contains such an easy password to guess. [Gro93]

The final vulnerability relating to passwords is usually perpetrated by vendors and software manufacturers. Sometimes the vendor software requires an account and password to operate. Usually (always?), the choice of password for this account is arbitrary, and generally not required by any human user. Unfortunately, many vendors and manufacturers in the past tended to install a "default" password, which made their life easier. Many customers when installing products were either not aware of the problem, or never got around to rectifying it. This allowed many intruders to simply access the system with a known account and password, often with full system privileges. It is fortunate that now many vendors are becoming more security conscious, and this practice is falling into disfavour. It does still occur however. Special care must be taken after applying upgrades as well. It is possible that this vulnerability was closed by changing the account's password, but the upgrade silently set it back to a known value.

One reason that passwords remain a major source of vulnerability is that they are out of control of the system administrator. Passwords are chosen by users, and if those users are not educated on what forms a strong password, then they will tend to choose a password that is easy to remember (which is also easy to guess or crack). It is therefore not surprising that there are a large number and variety of tools which are solely aimed at improving password security (for example, [Bis92b] and [Hoo90]). Ultimately, the solution to the password problem is to make use of these tools coupled with educating users that security is a community response and they have just as much responsibility if the computer system is compromised.

3.2.8. Software Vulnerabilities

There are many techniques that a programmer must employ when writing privileged code (or at least, code that will run as the Id of another user) [Far91b]. In essence, it is important to ensure that the user cannot control the environment in which the program executes, and that all error status returns are checked and handled appropriately. Failure to do this may result in the software integrity being compromised. These techniques are not reserved for privileged code, but are generally good coding practices. They include simple tasks such as initialising the operating environment to a known state, checking the status returns of all system calls, and parsing all arguments internally and not trusting a third party to do it correctly. Much of the rest of this section is drawn from [Bis87] and [GS91].

One particular type of attack involves the IFS shell variable (Input Field Separator). This variable is used to indicate what characters separate input words to the shell. Whilst its functionality has been largely superseded, it lives on to cause unexpected results. For example, if a program calls the system() or popen() functions to execute a command, then that command is parsed by the shell first. If the user has control over the IFS environment variable, then they may cause unexpected results. A typical scenario might be if the program executes the following code:

system( "/bin/ls -l ");

If the IFS variable has been set to contain the "/" character and a malicious program called "bin" is placed in the path of the user executing the program, then that program will be executed as the shell will have parsed the line as:

bin ls -l

which executes the program bin (in the current path) passing two arguments ls and -l. It is for this reason that a program should not get the shell to parse command lines by using the system(), popen(), execlp(), or execvp() commands to run some other program.

Another form of environment attack is the use of the HOME environment variable. Normally the csh and ksh substitute the value of this variable for the ~ symbol when it is used in pathnames. Thus if an attacker is able to change the value of this variable, it might be possible to take advantage of a shell file that used the ~ symbol as a shorthand for the home directory.

For example, if a shell file was referencing the ~/.rhosts file for the user running it, it is possible to subvert it by resetting the HOME environment variable before running it.

The final environment attack examined here (although these are not the only forms of environment attack, they do represent the classes of attack, and the methods to combat them are much the same), is the use of the PATH environment variable. This type of attack is characterised by the value and order of the files paths in the PATH variable. An inappropriate choice of path orderings may lead to unexpected results if a command is executed without reference to its absolute path. For example, consider the following PATH specification:

PATH = .:/usr/bin:/bin:/usr/local/bin

If someone had created a file called ls in a place that the current path is set to, then this would be executed in favour of the normal system /bin/ls command. If the file contained the following:

#!/bin/sh
(/bin/cp /bin/sh /tmp/.secret
/bin/chmod 4555 /tmp/.secret) 2>/dev/null
rm -f $0
exec /bin/ls "$@"

this would silently create a copy of /bin/sh which when executed would attain the identity of the person who executed the file. In addition, it would clean up any evidence and execute the real /bin/ls command so that the command would ultimately succeed without the user being aware that something else has happened. This is a typical example of a Trojan Horse (see Section 3.2.12).

In general, the use of relative path names either in referencing files or when executing programs should be considered as a poor programming practice.

Software vulnerabilities also are made apparent through the use of poor programming practices. These practices may be performed through ignorance or inexperience, or the programmer may simply be too lazy to do it right the first time.

A prime example of this was exploited by the Morris worm. The vulnerability was that the gets() system call was used which does not perform any length checking. Instead the fgets() system call should have been used. This allowed a buffer to be overflowed under the user's control, and hence the program was forced to take inappropriate action. There are several other system calls that suffer this same fate, including scanf(), sscanf(), fscanf(), and sprintf(). Software system design may affect the use of strcpy(), bzero(), and bcopy() as well.

Often, the value of umask (the default file protection mask) is set to something that is inappropriate. Many programs fail to check the value of umask, and often fail to specify a protection for any newly created files. Even if the program created a file, and then changed its protection to make it secure, a window of opportunity exists where an attacker could interrupt the program and possibly gain access to a writeable file. Therefore, it is important to establish a value for umask, prior to opening any files.

Another typical programming practice that can cause problems is the lack of making the effort to check the status returns on every system call. This means that if an intruder can gain control of the environment in which the program is running, they may cause a particular system call to fail, which the program blindly assumes will always work. This may cause the program to take further inappropriate action.

The final area covered here (which certainly does not form a comprehensive list, but details the typical mistakes made) is that often a program fails to catch all the signals it possibly can, and react appropriately. This may allow an attacker to set their umask value to something inappropriate, and the send a signal to a privileged program, causing it to dump its core. When this happens, the core file is owned by the effective UID of the running program, but protected with the value specified by umask. The dangers of this should be apparent!

3.2.9. Network Monitoring and Data Capture

One of the threats now being faced by computer systems is the ease at which data can be captured while it is being transmitted between computers [Bro93]. In the past when large central systems were the norm, this did not pose a large threat, but the advent of heterogeneous computer networks that span the globe mean that sensitive data may travel beyond the control of an organisation. There are many packages available that can be used to monitor data as it passes by on a network link. Particularly vulnerable are the bus style networks (such as Ethernet) where data destined for a particular host can be viewed by any host connected to the network.

This now means that any data can be captured and used for different purposes. This does not only include sensitive data, but may also include protocol exchanges (such as a login sequence, including the password). Authentication sequences are one of the most vulnerable exchanges as they are the critical decision point when granting access privileges.

Data does not always have to be captured from the network itself. By installing a Trojan Horse in the network software or the application, data may be captured and saved on the disk for later examination. This may defeat the standard defence of encrypting data that is to be transmitted across the network.

3.2.10. Examining Source Code

Since there are now known coding practices to avoid when writing programs (particularly ones that will run in a privileged mode), experience suggests that attacks are being launched at particular programs, usually after careful examination of the source code. In general, the source code is freely available and this allows anyone to examine it looking for potential flaws. This was not the usual form of attack up to five years ago, but it is becoming more common place these days [CER92]. In some aspects, this will result in some good as the programming errors will be finally rectified, and programming methodologies will become better understood.

This threat relates significantly to Section 3.2.8. It differs from that section in that most software vulnerabilities in the past have been discovered by accident. Now, the intruders are actively examining source code looking for any unknown vulnerabilities, which represents a new style of exploiting poor coding practices.

3.2.11. Protocol Weaknesses

Over the past few years, computer intruders have increased in number, age, and intelligence [Aus93]. Instead of exploiting old weaknesses and programming errors, they are now starting to target the actual design of distributed systems, in an attempt to fool one system into believing that the attacker is the systems partner. This may mean actively studying the source code of a system.

One of the more vulnerable areas of a systems design is when the system must interact with the external environment, particularly when requesting some service from another system. This exchange between the client and server systems may happen inside the same computer, or may be distributed across local and wide area networks. When the exchange is between two computer systems, some form of protocol is required so that the cooperating processes can indicate what function they wish performed, and to retrieve the results.

Many of the protocols in use for client-server models are now being actively examined by intruders in an attempt to find some weakness [CER92]. The intruders now have the tools to actively monitor network links looking for particular protocol exchanges, and then by careful programming, may insert a valid response to a request in an attempt to fool the requesting system into believing that it has received a correct response [GS91].

This is the type of attack detailed for NIS, but is certainly not restricted to just NIS.

3.2.12. Trojan Horses

Trojan Horses are named after the legend of the same name. Computer style trojan horses resemble normal programs that a user wishes to run, such as editors, login programs, or games. While the program may be appearing to do what the user wishes it to do, it is actually doing something completely different (such as deleting files, storing passwords for later use, reformatting disks). By the time the user is aware of a problem, it is far too late [KC90].

Trojan Horses can appear in many different places. They can be found inside programs that have been compiled, or in system command files executed by system administrators. Other forms of trojan horse include sending commands to people as part of a message (such as electronic mail or a message to a terminal). Some mail handlers allow the user to escape to the shell and execute commands. This can be done by activating the feature when the message is read. Sending a particular message to some terminals can have a command sequence stored in the terminal, and then have that command played back as though it was typed on the keyboard. Editor initialisation files are also a favourite place for storing trojan horses.

Trojan horses are unfortunately, very common [GS91]. As soon as a system is compromised, the attacker usually modifies the system in many ways to ensure that if the original intrusion is discovered, they can always get back into the system. This has the added effect of raising the cost of recovering from a compromise, as the entire system must be painstakingly checked for the presence of any Trojan Horses.

3.2.13. Spoofing Mail

A user receives electronic mail from their manager, telling them to change the privileged account password to "abcdefgh" as the manager suspects that one of the staff may be illegally using the account. The manager stresses to the user not discuss this matter with anyone. What does the user do?

"Spoofing" or falsifying electronic mail is extremely easy to do. Electronic mail is becoming an extremely important tool for efficient communication, and having a shadow of possible mail forgery hanging over its head has not necessarily slowed down its deployment, but it may have limited its effectiveness in many situations.

If business transactions are conducted using electronic mail in its basic form, then the potential for chaos and confusion is enormous. The possibility of forged orders, denied receipts, and challenging the issuing of directives means that electronic mail in this form is not a viable option. The ability to verify the source of electronic mail is becoming a very important issue. This will help establish services such as non-repudiation (the ability to prove that a user sent the message, and the ability to prove that a user received a message).

3.2.14. suid Files

Unix has the ability to set a special permission on files called SUID and SGID (set user identity, and set group identity). The meaning of these permissions is to change the effective identity of the user executing the program, away from their real user identity, to the identity or group of the owner of the program. This allows for example, a user to be temporarily privileged to perform some certain function, whilst being confined in an enclosed and controlled environment.

This is inherently dangerous unless the programmer is extremely careful. Whilst the program is executing as a different identity, subtle coding errors may allow the user executing the program to take advantage of the different user identity. If that user identity is privileged (root for example), then the potential for disaster is huge.

Many Trojan Horses make use of the SUID facility, by making a copy of the /bin/sh program (or similar), and setting the permission to SUID. This means that when that program is executed, a new shell is started, but it is now executing as the user that owns that shell. If that shell file is owned by root, then when the shell is executed, root privileges are attained.

3.2.15. World Writeable Files

World writeable files are files that can be written to by anyone who is validly authenticated to the computer system. This type of file presents a potential security risk as any information in those files can be replaced perhaps by installing a Trojan Horse to perform some other function, or used to gain access to that user's account and their privileges. A particularly vulnerable file is the .login file (or any such similar file) as it is automatically executed every time the user logs in to the system. Other files require the user to actively execute them before a Trojan Horse can be initiated.

On Unix, the devices are viewed merely as another type of file. If these devices are world writeable, then there is also potential for malicious use. Consider if the device associated with the hard disk is world writeable, an attacker could change any file on that disk by changing the physical block where that file is stored. Particularly vulnerable would be the /etc/passwd file, where an attacker could change the contents to anything they desired.

3.2.16. X Windows Security

X windows represents a different form of security vulnerability, as it more applies to an application rather than to a computer system. However, as a result of this, the system may then be compromised. Earlier versions of X windows had a crude form of security. With this, a system administrator could allow or deny particular hosts to display X clients on the X server. This was not user based, so once access was granted to a host, anyone on that host had access to the X server [MP92]. This resulted in many practical jokes in which people would randomly display pictures on the screen, or use a program to make the screen "melt" or "crumble" away. These practical jokes had more of a nuisance value than a security risk.

There are now programs that will attach to the X server screen, and so the intruder can see exactly what is being displayed on the screen at any point in time. Other programs capture every keystroke (including shift and control keys). The potential for damage here is that it is usually easy to see a password being typed in. There are now facilities in place to provide per-user authentication, but they tend to be so cumbersome that the less-security conscious users tend to avoid them. Work in this area is still progressing.

3.2.17. Software Interaction and Configuration

Ultimately, the underlying reason for security problems can be reasoned to stem from the fact that computer systems and the software that run them are becoming more complex each day. Given that generally no single person writes the entire system, it is impossible to predict the interaction of several components of a system, especially at the border conditions and in obscure error cases. Programming methodologies are being tightened up, but it is definitely a case of "too little, too late".

As well as the complexity of software interaction, programmers are giving the system administrator a huge range of choices. The job of configuring a system is becoming so complex that simple errors may lead to subtle security problems. Finding staff who are experienced in a wide range of applications and system configurations is difficult, and they have mostly gained that experience the difficult way. There is a trend towards electronic testing of computer systems security; evaluating the ability to penetrate the system through the use of programs, both external and internal to the system itself. This however only reports on known vulnerabilities, and does little to detect new vulnerabilities.

3.2.18. Concluding Remarks

System intruders are definitely becoming more sophisticated. It was stated in a conference recently that the average age of the intruder is increasing, and law enforcement are no longer dealing with wayward teenagers. They are now arresting alleged intruders who have graduated with First Class Honours degrees [Aus93]. This is also reflected in the types of attacks that are being witnessed over time.

In 1988, the majority of computer system intrusions resulted from exploiting poor passwords, and system vulnerabilities.

In 1993, the techniques of five years ago are still in common use (and are still successful!). However, intruders are now exploiting protocol weaknesses in attempt to fool servers into performing some service, there is more network sniffing looking for valuable information, and many of the intruders are found with original source code most likely with a view of examining it for more flaws.

As the sophistication of intruders grows, so does the sophistication of the system administrators and their tools need to grow. It has been suggested in brochures distributed by the Australian Computer Society that knowledge in the Information Technology industry is doubling every five years. This appears to correlate with the experiences shown above.

3.3. Summary of Threats and Classification

Having detailed a number of typical threats that computer systems have faced and are still facing, it is interesting to note how they can be categorised under standard criteria. Using the threats defined in X.509 [ISO92], Table 3 attempts to perform this analysis. However, it turns out to be quite difficult to do this while adhering to the strict definition of the standard threat criteria.

Due to these difficulties, structuring the information in this manner does not provide as much information as perhaps some other format. Further work in this area is required.

Table 3 - Categorisation of threats
Data
Interception
MasqueradeReplay ManipulationRepudiation Misrouting
Worms

Morris
WANK
OILZ


x
x
x

O
O
O

x
O
O

O
O
O

O
O;
O;

x
x
x
Trusted Systems

hosts.equiv
.rhosts
secure terminal


x
x
x

O
O
O

x
x
O

O
O
x

O
O
O

O
O
x
tftp
O
O
O
x
O
x
NIS

+ in passwd
ypset
ypserv
NIS Packets


x
O
O
O

O
O
O
O

x
O
O
O

x
O
O
O

O
O
O
O

x
O
O
O
NFS

exports
file handles


O
O

O
O

x
O

O
O

O
O

x
O
Passwords

guessing
cracking
guest
default


x
O
x
x

O
O
O
O

x
x
O
O

x
x
x
x

O
O
O
O

x
x
x
x
Software

IFS
HOME
PATH
umask
error returns
signals


x
O
O
O
x
O

O
x
O
x
x
x

x
x
x
x
x
x

O
O
O
O
O
O

x
x
x
x
x
x

O
O
O
x
O
O
Monitoring

network
trojan horse


O
O

x
O

x
x

x
O

O
x

x
x
Source Code
O
x
x
O
O
x
Protocol Weakness

spoofing


O

O

O

O

O

O
Trojans

part of message
in programs


O
O

O
O

x
x

O
O

O
O

O
x
Spoofing Mail
O
O
O
O
O
O
suid Files
x
O
x
O
O
x
World writeable files

files
devices



O
O


O
O


x
x


O
O


O
O


O
O
X Windows
O
O
O
O
O
O
Interaction
Configuration
O
O
O
O
O
O

4. Analysis of Security Tools

The threats that are detailed in the previous chapter are generally well known, and solutions are in place to combat them. However, it is impossible to say that a system is entirely secure, and so careful vigilance is required to ensure the integrity of a computer system, its software, and the data stored there.

A number of tools and techniques are available to help the system administrator and system programmer with their task. This chapter includes a wide selection of them with a discussion on what they do and what threats they are attempting to combat. They are broken into five broad sections: Cryptographic Tools, Monitoring Tools, Security Assessment Tools, Security Enhancement Tools, and Programming Methodologies.

4.1. Cryptographic Tools

4.1.1. DES

One of the most widely used encryption systems today is the Data Encryption Standard developed in the 1970s by IBM [FIP77], [CLS91]. DES is basically a bit permutation, substitution, and recombination function performed on blocks of 64 bits of data and 56 bits of key (8 characters of 7 bits). The algorithm is structured in such a way that changing any bit in the input has a major effect on almost all of the output bits.

The DES algorithm can be used in four modes:

Electronic Code Book (ECB);

Cipher Block Chaining (CBC);

Cipher Feedback (CFB);

Output Feedback (OFB).

Each mode has particular advantages in some circumstances, such as transmitting data over a noisy channel, or when it is necessary to decrypt only a portion of the file. The four modes are described below.

ECB Mode - In electronic codebook mode, each block of input is encrypted using the key, and the output is written as a block. This method is simple encryption of a message, a block at a time. This method may not indicate when portions of a message have been inserted or removed. It works well with noisy transmission channels, as alteration of a few bits will only corrupt a single 64-bit block.

CBC Mode - In cipher block chaining mode, the plaintext is XOR'ed with the encrypted value of the previous block. The result is then encrypted using the key. Because bits everywhere in the message propagate through the entire encrypted output, the last block can be used as a signature that the encrypted text has not been altered. As well, long runs of repeated characters will be masked in the output.

CFB Mode - In cipher feedback mode, the output is fed back into the mechanism. After each block is encrypted, part of it is shifted into a shift register. The contents of this shift register are encrypted with the user's key using ECB mode, and this output is XOR'ed with the data stream to produce the encrypted result. This method is self-synchronising, and enables the user to decipher just a portion of a large database by starting a fixed distance before the start of the desired data.

OFB Mode - In output feedback mode, the output is also fed back into the mechanism. A register is initialised with some known value. This register is the encrypted with ECB mode using the user's key. The result of this is then used as the key to encrypt the data block (using an XOR operation), and it is also stored back into the register for use on the next block.

DES uses the same key to encrypt the data and decrypt the data. Therefore, it is essential to use techniques that keep the secrecy of this key intact. Poor key management will lead to the reduced effectiveness of DES.

4.1.2. MD2

The MD2 Message Digest Algorithm [RFC1319] was created as part of the Privacy Enhanced Mail package (see Section 4.1.5). MD2 generates a 128-bit signature (fingerprint or message digest) from a given block of text. The signature is designed to prevent someone from being able to determine a block of valid text from a given signature or allowing them to modify the block of text while maintaining a valid signature. It uses 18 rounds of table substitution. The MD2 signature is a relatively slow algorithm compared with MD4 or MD5.

4.1.3. MD4

The MD4 Message Digest Algorithm [RFC1320] generates a 128-bit signature from a given block of text. It was designed to exploit to 32-bit RISC architectures to maximise its throughput, and does not require large substitution tables. "Because MD4 was designed to be exceptionally fast, it is "at the edge" in terms of risking successful cryptanalytic attack." [RFC1320]. MD4 uses three rounds of transformations.

It has been shown that if MD4 is stripped of its last round, then for 99.99% of all initial values, it is possible to find two messages differing in only 3 bits which hash to the same output value. If MD4 is stripped of its first round, it is possible to find for a given initial input, two different messages which hash to the same value [BB91]. It is claimed that as a result of this attack, changes were made to MD4 to develop MD5 [BB93].

4.1.4. MD5

The MD5 Message Digest Algorithm [RFC1321] is a proposed data authentication standard. MD5 attempts to address potential security risks found in the speedier but less secure MD4. MD5 is a more conservative algorithm that attempts to reduce the risks of a successful cryptoanalysis attack by not adding further complexity. MD5 also produces a 128-bit signature, and uses four rounds of transformations to ensure pseudo-random output.

4.1.5. PEM

PEM is Privacy Enhanced Mail [RFC1421], [RFC1422], [RFC1423], [RFC1424], a standard for allowing transfer of encrypted electronic mail. It is actually a protocol that is designed to allow the use of private key (symmetric) and public key (asymmetric) cryptographic techniques. PEM is designed to provide message integrity checking, originator authentication, non-repudiation of origin, and data confidentiality. These are offered through the use of end to end cryptography between the originator and the recipient. There are no special requirements imposed on the message transfer system. This allows incorporation of the PEM system without impact on existing systems.

In general, the message is encrypted using a randomly chosen private key (the Data Encryption Key), and that key is then encrypted using the recipients public key. In that way, only the recipient of the message can decrypt the chosen private key (using their asymmetric secret key), and therefore decrypt the message. To provide message integrity and data origin authentication, the originator generates a Message Integrity Code and signs this with the private component of a PKCS key pair.

4.1.6. RIPEM

RIPEM is one implementation of the Internet's Privacy Enhanced Mail [Hey93a]. It allows electronic mail to have the four security facilities provided by PEM:

disclosure protection;
originator authenticity;
message integrity;
non-repudiation of origin.

RIPEM is not a complete implementation of PEM as described in the RFCs. PEM specifies certificates for authenticating keys, which RIPEM currently does not handle.

In RIPEM, key management is performed in three ways:

a central server;
distributed finger servers;
a flat file.

None of these facilities provides perfect security and is a recognised weakness of RIPEM [Hey93b]. For a discussion on certificates and key management, see [Smi93].

This differs to TIS/PEM which makes use of certificates to bind the name to the public key. RIPEM is freely available within the United States and Canada, but due to export restrictions, the cryptographic sections are not available outside those countries.

4.1.7. TIS/PEM

TIS/PEM is a different implementation of PEM which was developed by Trusted Information Systems Inc [TIS93]. It is a reference implementation developed in cooperation with RSA Data Security Incorporated, and is sponsored by ARPA. TIS/PEM is freely available within the United States and Canada, but due to export restrictions, the cryptographic sections are not available outside those countries.

TIS/PEM uses certificates to bind names to RSA public keys and is capable of generating the required certificates. Joining the Internet certification hierarchy makes it easier to verify others' mail and for them to verify yours.

4.1.8. PGP

PGP is a cryptographic mail program called Pretty Good Privacy [Zim92]. PGP makes use of RSA, but does not conform to the PEM RFCs. It is not a "standard" as such and is therefore not compatible with anything else but itself. PGP does not make use of RSAREF, but its own implementation of RSA which has not been licenced in the United States of America. This has recently raised a number of legal issues which are still unfolding.

Whilst PGP has more key management facilities than RIPEM currently does, it still does not provide the hierarchical structure of certificates, which is required to determine ultimate trust.

PGP is currently involved in legal issues, and it is not compatible with other systems. This will possibly reduce its lifetime as a viable solution.

4.2. Monitoring Tools

The Monitoring Tools can be used to examine network traffic, looking for set patterns of data or connection attempts.

4.2.1. TAP

TAP is a SunOS specific tool at present [Ney92]. It is a loadable kernel module that examines data as it passes through a stream and captures it for examination. This is performed by inserting the TAP module between two kernel modules (e.g., ttcompat and ldterm), and having these modules pass their M_DATA messages to TAP. TAP makes a copy of the data and sends it off to a specific driver, and then forwards the message onto the next kernel module for processing.

Given that TAP requires a module to be loaded into the kernel, it is inherently a dangerous tool. If any coding errors or kernel module incompatibilities exist either in this version or future versions of any software, then the likely result is a system panic. Another problem is that the knowledgeable user may be able to detect that they are being monitored. If TAP is used to monitor a malicious intruder, then this may be all the encouragement the intruder needs to proceed with destroying the compromised system.

Most techniques of monitoring people by making use of the computer system that the person is logged into can usually be detected if the person being monitored is vigilant. Therefore, it is much better to take the monitoring facility away from the host, and move it somewhere that the intruder cannot access (such as another machine connected to the network). This can be particularly effective as these days, most data travels across a network of some form, and it also allows several systems to be monitored at once.

4.2.2. tcpdump

tcpdump is a tool that listens to the network, and saves any packets that meet some specified criteria [MLJ92]. If the network controller is an Ethernet controller, it is placed into promiscuous mode first, so that all data on this bus network is examined by the networking software. Note that on some systems, it is not required to be privileged to place the controller into promiscuous mode.

tcpdump has two main modes of operation: the first is to display the packets on the screen as they are captured, and the second is to log the packets to disk in a binary format for later processing (using tcpdump again). In general this tool is designed for examining network problems, and does a good job of displaying the protocol headers. However, it is possible to specify how many bytes of each packet are to be saved, and this can be increased to include the data in a packet as well, therefore allowing anyone to view any network data.

This package can be tailored to only display or save relevant data. When the program is executed, a number of filters can be specified. These filters can select packets based upon the following criteria:

a single host, or a range of hosts;
source address, destination address, or either;
a particular port number;
a particular protocol type;
less than or greater than a specified length;
broadcast or multicast packet.

These primitives may be combined with boolean operators to form a complex expression. For example:

host 1.2.3.4 and net 1.4.7 and (port telnet or port mail)

Saving larger packets (or more data from each packet) increases the chances that a packet will be dropped due to insufficient resources. Despite this, tcpdump is a useful tool, not only for analysing a TCP/IP network.

4.2.3. TAMU etherscan

etherscan is a network monitoring tool that monitors certain TCP/IP services for activity that indicates possible intruder presence [SSH93]. It scans all packets and their contents looking for a specific set of intrusion signatures, such as root login attempts from remote sites. etherscan also acts as a complement to the drawbridge filter package (see Section 4.4.12) by covering areas of weakness inherent in bridging filter arrangements.

4.2.4. TAMU netlog

netlog contains three programs to log traffic from TCP and UDP connections [SSH93]. It can be used to locate suspicious network traffic. The tcplogger logs all TCP connections on a subnet, the udplogger logs all the UDP connections on a subnet, and extract processes the log files created by tcplogger and udplogger. These logger programs record a one line summary for all connection attempts. The associated analysis programs report on suspicious connections or patterns of connections.

4.2.5. Supervisor

Supervisor is a VMS only tool at present [Goa92]. It displays all I/O to a terminal device to another terminal device. Supervisor offers two monitoring modes: Observer and Advisor.

Observer mode allows a privileged user to view every character typed and printed at another user's terminal. The effect is that both users, initiator and target, see the same screen at the same time.

Advisor mode allows a terminal user to type command lines, comments, and program input into the target user's terminal process. The effect is the same as if the target user had typed the characters.

Photo is a similar utility that allows users to log their own terminal sessions into a file. While Photo is running, each character sent to and received from the terminal is recorded in a file and may be redisplayed later using the playback command.

4.3. Security Assessment Tools

This section describes tools that can be used to assess the security of the computer system. Each tool addresses different aspects f computer security.

4.3.1. COPS

COPS (Computer Oracle and Password System) is a collection of programs than individually examine a particular aspect of Unix computer security [Far91a]. These programs make no attempt to rectify any vulnerabilities they encounter, but merely report them. COPS examines at least the following list:

file, directory, and device permissions/modes;

poor passwords (using a simple password cracker);

content, format, and security of password and group files;

the programs and files run during system startup, and those run at regular intervals;

the existance of any root SUID files, their writeability, and whether they are shell scripts or not;

a CRC check of important binaries and key files to detect any changes;

writeability of users home directories and startup files (.profile, .cshrc, and so on);

anonymous ftp configuration;

unrestricted tftp, decode alias in sendmail, SUID uudecode problems, hidden shells inside inetd.conf, rexd running in inetd.conf;

miscellaneous root checks (current directory in path, a "+" in hosts.equiv, unrestricted NFS mounts, ensuring root is in /etc/ftpusers, and so on);

dates of released vulnerabilities versus key files in an attempt to determine if there are binaries that are known to contain problems;

the Kuang expert system which takes a set of rules and tries to determine if the system can be compromised.

The Kuang Rule-based Security Checker is supplied as part of the COPS package. The heart of the Kuang program is a set of rules that describe the Unix protection system from the point of view of an attacker. For example, one rule says that if an attacker can write the file /etc/passwd, then that attacker can gain super-user access. Given a goal like "become super-user", the Kuang system examines all the rules to produce a list of subgoals that would be sufficient to meet its goal. Recursively, each subgoal can be processed to produce a list of further subgoals. The process continues until there are no new goals. At each step, if a goal can be achieved using the initial privileges of the attacker, Kuang prints out a message that describes the sequence of steps that leads from the initial goals to the target goal. An example of the output from Kuang is:

member project1, write ~tom/.cshrc, member staff, write /etc, replace /etc/passwd, become root

This means that the attacker had access to the group project1, which all members of project1 belonged to. The project1 group had write access to one of the startup command files executed automatically when the user tom logged in. Anyone on the project1 group could add commands to Tom's startup file, and have these executed with Tom's privileges next time he logged in. Tom is a member of the staff group, which can write to the directory that contains the passwd file. Write access to this directory allows members of the staff group to delete and recreate the passwd file, which may contain a rogue entry for the super-user account. Therefore, the attacker could gain access to super-user privileges.

The Kuang program attempts to make the task of verifying the security decisions easier for the security manager. It examines the relationship between the various aspects of the protection mechanisms supplied by Unix, to determine if it can achieve a goal based upon achieving a number of subgoals.

4.3.2. CRACK

Crack is a fast UNIX password cracking program designed to assist site administrators in ensuring that user's use effective passwords [Muf92]. It is approximately 16 times faster than standard DES routines, enabling one to check more passwords in a given time. Newer versions of the crypt() function may increase this speed by a factor of one to three, depending upon the configuration of the hardware.

Crack takes as input a series of password files and source dictionaries. It merges the dictionaries, turns the password files into a sorted list, and generates lists of possible passwords from the merged dictionary or from information obtained from the password file.

Crack makes several passes over the password entries. Each pass generates password guesses based upon a sequence of rules. These rules are specified in a simplistic language. The instructions in the rule are followed in order and are applied to the dictionary words as they are loaded. There is a simple pattern matching primitive so that select words can be ignored (thus saving time and memory).

There are two groups of rules. The first is a set of rules that is applied to the gecos field of the password file (which stores useful information about the owner of that account). This field might for example store the name, department, and telephone extension of the account owner. The gecos rules create passwords based upon permutations of that information.

Once a pass has been made over the passwords based on the gecos information, the package makes further passes by first applying the dictionary rules to the word lists supplied. The rules are applied to each word first to generate a memory resident dictionary which is sorted and uniqued to prevent any repetition. As well as standard dictionary words, a file can be supplied which details common passwords that are non-dictionary words. Examples are qwerty or drowssap (password backwards).

If Crack successfully guesses a password, it marks the user as done and does not waste further time on trying to break that user's password. Crack also has the facility where passwords that were guessed on previous runs will not be checked again if they have not been changed yet. These passwords are trivially and instantly cracked.

Crack has the ability to automatically spread the load of password cracking to several machines on a network. The hosts are specified with a "power" rating which aids in determining how much load to pass to each of the networked machines. The power is determined by assigning the power of one (1) to the slowest machine, and rating every other machine relative to it. More accurate figures can be obtained by using a program to determine how many password encryptions a machine can perform in one second, and using that figure as the relative power of the machine.

Once Crack enters the second pass over the passwords, it periodically saves its state. This state can be used to recover from that point should a host crash, or Crack aborted. This helps to avoid the problem of programs with long run times from never completing if the relative uptime of the machine they are running on is less than the expected runtime for the program.

4.3.3. Tripwire

Tripwire is a file integrity checker using a number of cryptographic checksumming algorithms in parallel for added security [KS92]. Algorithms such as CRC-16 and CRC-32, commonly used to checksum data packets for transmission across a network [Tan89], do not provide sufficient strength to protect the integrity of files against a determined intruder. There are public domain tools that will help to "recreate" a valid checksum on files, while still maintaining file size. This is especially true of system binaries.

Tripwire makes use of several message digesting algorithms. These are:

MD5
MD4
MD2
Snefru
CRC-32
CRC-16

The use of more than one of these algorithms in parallel greatly decreases the chances of an intruder being able to modify a monitored file without detection. To run Tripwire, a reference database of results needs to be built, immediately after the installation of the operating system and any products, and prior to reconnecting to the network. In this way, it is possible to be sure that the files have not already been modified. The output of Tripwire (as well as Tripwire itself) should ideally be kept on a hardware write protected disk to prevent modification. Tripwire should then be run at regular intervals to verify the integrity of key system files. Another alternative to using hardware protected media is to print out a copy of Tripwire's results. In this way, an intruder must gain physical access to the premises to adjust the original data from Tripwire. This also helps if there is any suspicion on the integrity of the Tripwire database.

Note that it would be meaningless to use Tripwire to protect a file such as the system password file as users have the ability to change their password at any time, and thus the file checksums will also change.

4.3.4. TAMU tiger

tiger is a set of scripts that scan a Unix system looking for security problems, in the same fashion as COPS [SSH93]. In addition, it includes a file checksumming facility using Xerox's cryptographic checksum programs to check for both modified system binaries, as well as the presence of required security related patches. This is similar in operation to Tripwire.

4.4. Security Enhancement Tools

Security Enhancement Tools can be used to improve the security of a computer system, particularly to combat a known threat.

4.4.1. TCP Wrapper

TCP Wrapper (also known as LOG TCP) is a package that is used to monitor incoming IP connections, log them, and provide a number of add-on services including a limited form of access control and some sanity checks [Ven92].

The first function is to log connections. Any connection to an IP service that has TCP Wrapper enabled for it will write a connection record to the syslog daemon, containing the time and the source of the connection.

If the access control has been enabled, the list will be checked to see if the source of this connection has been allowed or denied access to that IP service. If the service is denied, the connection is aborted. If the service is allowed, then the normal daemon is executed.

If the name checking has been turned on, the wrapper will verify that the name to address mapping is the same as the address to name mapping. If there is any discrepancy, the wrapper concludes that it is dealing with a host that is pretending to have someone else's name (as in an attack on the "r" commands). If this is detected, it is logged and the connection aborted.

TCP Wrapper is an extremely simple, and yet effective tool. It is very useful in preventing connections from outside an organisation from approaching the systems. It is possible to allow certain connections (for example, mail) to the systems, while restricting others. Even if an intruder learns an account and password for the system, they must first penetrate a "trusted" system before they can gain access to the system. It is therefore imperative that users do not use the same password on all systems.

4.4.2. socks

The socks package is a firewall enhancer which allows users to pass out through the firewall transparently [KK92]. Many networks are now moving towards the concept of firewall systems - systems designed to act as an entry portal to the network. Using a firewall system means that no other systems on that network are directly connect to the global network. To access the global network requires accounts on the firewall system, and a two hop process. Granting a large number of users access to the firewall system reduces its effective security, and therefore its performance as a security tool.

Socks automates the process of having a firewall host which is utilised as a transient point for global network access. Although socks itself does not enhance the security of the host it is running on, it does enhance the security of the firewall system by not requiring that every user needing global network access have an account on the firewall. Now the security of the firewall can be more effectively controlled.

From the point of view of a user behind the firewall host, there is no apparent difference between running socks and the regular client software on a host. All connections at the application level will appear to work the same, with the hidden difference that all traffic is passing through a daemon on the firewall host. This transparency is achieved through the socks library routines which applications use in place of the normal socket library calls.

Socks also maintains a security configuration file, which can filter connections based upon host address and service. This is very similar to what TCP Wrapper does, except TCP Wrapper operates on a host by host basis, and the socks package is designed to filter all connections to the local network that pass through a firewall system.

The socks daemon records logging information via syslog, which includes:

access denied
successful connection
resource failure

Not e that one of the major issues relating to use of socks is the performance of the firewall system. The configuration of the firewall must be chosen carefully to cope with the burden of supporting all external network connections through the central point. However, since the major task of a firewall machine is to filter packets or allow them through (that is, it will now contain fewer user accounts), there will be more processing power available to perform this task adequately.

4.4.3. passwd+

passwd+ is a replacement for the Unix password change program /bin/passwd, designed to prevent weak password choices before they are used [Bis92b]. passwd+ takes a configuration file which defines a series of tests. If the proposed password passes any of these tests, it is rejected as being too weak. The tests are configurable by the system administrator to suit local conditions.

passwd+ has the ability to log information to a number of places:

as input to a command;

appended to a file;

written to the system logger syslogd;

written to standard error.

The level of logging may vary from logging errors with the program, to the result of the password tests and who used the system.

These suggested checks include:

dictionary words;

single case passwords;

minimum length;

patterns.

4.4.4. npasswd

npasswd is another replacement program for /bin/passwd, the program that changes a user's password [Hoo90]. This program deals both with normal password files, and with NIS password files. npasswd performs the following checks:

dictionary words;

single case passwords;

minimum length;

illegal characters in passwords (as defined by the system administrator).

npasswd checks the passwords and disallows them if they are considered to be insecure. It is possible to perform logging to the system logger syslogd.

npasswd is quite similar in functionality and intent with passwd+, however, passwd+ offers a richer set of tests and configurations to npasswd.

4.4.5. secure_lib

secure_lib contains replacement routines for three SunOS kernel calls: accept(), recvfrom(), and recvmsg()[LeF92]. These replacements are compatible with the originals, with the added functionality that they check the Internet address of the machine initiating the connection to ensure it is authorised to connect. A configuration file defines what hosts are allowed for a given program. Once these replacement routines are compiled, they can be used when building a new shared libc library. The resulting libc.so can then be put in a special place. Any program that should be protected can then be started with an alternate LD_LIBRARY_PATH.

The secure_lib system is especially effective as it provides protection for all daemons, rather than a select few. This allows new daemons to be added to the system, and they can be protected with the same level of confidence. Only the three nominated SunOS kernel calls are replaced. Other kernel calls read data from the network, but only if data is read from a connected socket. Only accept() can generate file descriptors for connected sockets, so protecting accept() will also provide protection for these kernel calls.

Judicious use of this shared library may prevent rogue users from generating their own daemons, providing services from your system that were not intended.

4.4.6. Kerberos

Kerberos is a distributed authentication system designed to prevent many of the types of common attacks seen today. Kerberos has a number of design criteria which is used to combat some of these threats. The first is that passwords are not transmitted across the network in plain text. This prevents network sniffing of the password and its reuse. The second is that authenticators contain a timestamp which is used to help prevent replay attacks.

Section 2.5 provides a detailed description of Kerberos.

4.4.7. Password Ageing

Yet another problem in the long list of password usage deficiencies is the problem of users not changing their passwords regularly. Even if a secure password is chosen (one that is difficult to guess or crack), the longer the password remains in use, the more chance there is of having that password compromised.

Advances in computer technology are continually reducing the time required to attack a password using the brute force approach (by encrypting every possible password combination and checking them). New speed records place encrypting every possible six (6) character Unix password at less than one hour. [Mad93]

This computation time can only get smaller unless the algorithm is universally changed. Therefore, it is imperative that users choose passwords that are of sufficient length, and change them regularly.

One mechanism for ensuring this happens is to use pro-active password checkers (for enforcing the minimum length - see passwd+ and npasswd), and applying a password ageing scheme to force users to regularly change their passwords. Some Unix systems support these schemes by default [Far91b], but others require an add-on package to implement this feature (password ageing is sometimes implemented as a part of other password enhancement packages, for example [Hei90]).

4.4.8. Token Generators

Token Generators are hardware packages that implement password "tokens", or one-time use passwords [Ell92], [CER92]. Token generators are implemented using a variety of schemes.

One system operates by challenging the user with a seven digit number (in phone number format). A PIN number and the challenge number are entered into the hand held device, and it gives a seven digit response code to reply with.

Other systems use a changing, non-reusable password system. Each time a user authenticates, a new password is supplied by the hand held device. There is no challenge-response system, and the user must keep in synchronisation with password usage to prevent a denial of service. Some of these systems can support single use password generation for up to eight separate host systems. Some of these systems require the user to enter a PIN before the next password is issued.

Another system displays the password continuously, changing it every minute or so. The host must not only keep the user's key (for generating the same sequence), but also a synchronised clock.

The one-time password system is extremely effective in preventing replay attacks, provided the enemy does not know the sequence of generated passwords (either by guessing, or possession of a similar device and key).

One of the major disadvantages is that to authenticate, a user must carry the hardware with them at all times. If they do not possess their hand held device, then authentication cannot take place.

Some of the systems have a requirement for synchronised clocks. This may cause the system to suffer a denial of service due to clocks slipping, or an attacker targeting the clock synchronisation protocols to set the time to any desired value.

4.4.9. S/Key

S/Key is a software system designed to implement a secure one-time password scheme [KHW93]. It uses 64 bits of information transformed by the MD4 message digest algorithm [RFC1320]. The 64 bits are supplied by the user in the form of six English words that are generated by a secure computer. Ultimately, this computer could be a pocket sized smart card, a standalone PC or Macintosh, or a secured machine at work.

The package consists of a number of programs. keyinit initialises a user to the S/Key database and requires them to enter a secret password. key is then used to generate a number of one-time passwords for future use. key requires the user to enter their secret password. Note that if the user does not correctly enter the secret password, a number of one-time passwords will still be generated, but they will not be valid.

There are also replacement programs for the login and su programs which prompt for the one-time password with a challenge. The output looks like the following:

host# ./keyinit
Adding root:
Reminder - Only use this method if you are directly connected.
If you are using telnet or dial-in exit with no password and use keyinit -s
Enter secret password:
Again secret password:

ID root s/key is 99 sh42277
SITU LEFT FLEW MALT MEL PUN
host#
host# key -n 3 99 sh42277
Reminder - Do not use key while logged in via telnet or dial-in.
Enter secret password:
97: MASK THIS WART RUE ANNA IRON
98: TALL TOY CALF AWN HOOK LIT
99: SITU LEFT FLEW MALT MEL PUN
host#

Now when a user wishes to use the one-time passwords, the following happens:

host> su
s/key 99 sh42277
Password: !enter SITU LEFT FLEW MALT MEL PUN
host# ^D
host> su
s/key 98 sh42277
Password: !enter TALL TOY CALF AWN HOOK LIT
host#

If the wrong password sequence is entered, it is treated as the same as an incorrect password.

keyinit also operates with a secure option if the user is logged in via an insecure network or communications link. This modifies the behaviour of keyinit by requiring a valid pre-existing one-time password, as follows:

host# keyinit -s
Updating root:
Old key sh42277
Reminder - You need the 6 english words from the skey command.
Enter sequence count from 1 to 10000: 99
Enter new key [default sh27276]: sh42277
s/key 99 sh42277
s/key access password: SITU LEFT FLEW MALT MEL PUN

ID root is 99 sh42277
SITU LEFT FLEW MALT MEL PUN
host#

From here it is possible to run key to generate a list of valid one-time passwords as before. It was not immediately obvious that the secure option provided much more security than the normal package. Initial observations were that the only difference was that the with the "insecure" option, a small secret password was entered, and with the "secure" option, a previously (any previously) valid one-time password id and sequence was entered. If someone is monitoring the network, they may already have seen a valid one-time password exchange, and therefore be armed with that information already. In fact, the "secure" option allows the user (requires the user) to specify the key, thus opening up a possibly previously used range of passwords for reuse. It was considered the "secure" option to actually be less secure than the "insecure" option for these reasons.

Discussions with the system developers indicate that the intention is not to use a previously valid password, but to generate a valid password, id, and sequence tuple which will allow the user to change to the new id over an insecure network without revealing their secret password. This generation of the next password is done whilst in the secure environment. In fact, once the secret password is set, it need never to transmitted over an insecure network again. It is considered that this was not clearly detailed in the documentation, allowing for confusion which could lead to a lowering of the security of this system.

Despite this misgiving, the S/Key system is an extremely useful and simple mechanism for implementing secure one-time passwords.

4.4.10. Shadow Passwords

One of the major problems with the Unix password system is that the password file is world readable. This is required as many applications need access to parts of the password file for a variety of reasons. Leaving the file world readable means that those applications do not need to be privileged to perform their task.

However, whilst the cryptographic algorithm that encrypts these passwords is still quite strong, the choice of the original password by users still leaves something to be desired. This means that by taking a copy of the password file to another system and running a password guesser on it, it is usually possible to guess at least one password, thus allowing access to the system.

There are a number of solutions to this problem. Shadow Passwords operate by not providing the intruder with a copy of the passwords in the first place. Therefore, the intruder is forced to guess the password by attempting to login, and this can be centrally detected and combatted.

With shadow passwords, the password field in /etc/passwd is replaced with some meaningless data (sometimes a "*", sometimes "##", sometimes the username), and the real passwords are stored on a non-world readable file elsewhere. The system programs that access the password field are privileged anyway, and they only need modification to know when to look in the shadow password file.

Shadow passwords are provided by default on some Unix systems [Arn93], but are required to be added-on for other Unix systems (for example, [Hei90]).

4.4.11. Security Extensions to TCP Transport Layer

Work is currently underway to develop security extensions to the TCP transport layer [Bro93]. This involves a modified implementation of the Secure Data Network System protocols [Din90]. The basic system provides authentication through a challenge-response mechanism, and encryption of the exchanged data. This helps to prevent network monitoring. Extensions have currently been added to the FTP and TELNET application layer protocols, and work is progressing to implement a system that works for all TCP and IP connections. This requires a greater modification to existing systems that those currently performed for the applications.

4.4.12. TAMU drawbridge

drawbridge is a bridging filter system that runs on a PC using two ethernet cards [SSH93]. It is composed of three different tools: filter, filter compiler and filter manager. By running this type of software on a local PC, it allows specific configurations to be tailored, without affecting all users on a multi-port router. Often, multi-port routers do not have sufficient capacity to route multiple protocols, while providing intelligent filtering functions. In addition, if the router's security is compromised, then the network security is not vulnerable. The PC is not designed as a multi-user machine with remote configuration abilities.

4.5. Programming Methodologies

Whilst some of the problems with security on computer systems are related to design, the proliferation of third party software packages has opened up a new world of security vulnerabilities. Often here, the problems relate to either inexperienced programmers or inadequate care when coding the system. The problem of how to write secure systems has been analysed for many years, and it is possible to write highly secure programs, if a number of basic mistakes are not performed. If it were not possible to secure computer systems, then they would not be connected to global networks.

Many of the solutions and programming styles simply come down to being as conservative as possible with programming, and never trusting the environment the program is operating in. The points detailed here are taken from [Bis87], [Far91b] and the author's own experience.

4.5.1. Close file descriptors

Close all unnecessary file descriptors before calling exec(). exec() has a documented feature than when the new program is called, "descriptors open in the calling process remain open in the new process" [Sun90c]. This means that if the first program was reading a sensitive file using privileges, and calls a user program via exec() without closing the file descriptor to that sensitive file, then the user program will also have access to that file.

4.5.2. Check the environment in which the process will run.

This basically means that the program should set up the entire environment to exactly how it requires it. This may involve setting known values in environment variables like HOME, PATH, and IFS, setting a valid umask value, and initialising all variables.

4.5.3. Never use relative filenames.

This is closely related to the previous problem of determining the environment. Relative filenames and program names allow an intruder to determine the exact location where the file will be read or written to, through control of the environment.

4.5.4. Catch all signals.

This one is not always obvious. When a process dumps core (terminates abnormally taking a copy of the memory image onto disk), the owner of the core file is the same user identity as the effective user identity of the running process. If this process is running as a SUID process, the user identity may be different to the person running the program. If the umask value has been set to something inappropriate, then it may be possible to write over the core file, while maintaining its original ownership. However, there may be race conditions introduced when catching signals. Signals can be safely ignored, but it may not always be appropriate to do this.

4.5.5. Make only safe assumptions about error recovery.

A privileged program can never assume that all will be well when performing operations. Subtle errors may occur that were never expected (such as not being able to access a file, or running out of file descriptors). These errors must be handled correctly. Do not attempt recovery unless the recovery is guaranteed. It is too easy to open the program up to taking inappropriate action once it loses control of its environment.

4.5.6. Verify all input data.

It is too easy to assume that just because one program wrote the input data, that it will always have the correct format and be valid. Data should be bounded, and verified for syntactic correctness, integrity, and origin if possible. Just because it is stored in a protected file is not sufficient grounds for waiving the responsibility for verifying the data. Any input data under the direct or indirect control of a user is particularly risky, and should always be treated with the utmost of suspicion.

4.5.7. Beware race conditions.

An example of this might be that the program creates a file, and in the next command protects it. In between the two commands, a window of opportunity may exist for an intruder to gain access to a poorly protected file that allows them to gain further access to the system. Attacks against race conditions (once identified) are generally automated, thus allowing the window of opportunity to be explored at a much greater rate than by hand. Never leave the configuration vulnerable, not even for a single instruction.

4.6. Tools use to Combat Threats

A useful exercise is to look at the threats outlined in Chapter 3, and determine which tools outlined in Chapter 4 can be used to combat those threats. Table 4 details that analysis.

Table 4 - Tools use to combat threats
Worms

Morris

WANK
OILZ


Secure Programming Methodologies, passwd+, npasswd, password ageing, S/Key, Token Generators
password ageing, S/Key, Token Generators
password ageing, S/Key, Token Generators
Trusted Systems

hosts.equiv
.rhosts
secure terminal


COPS, TCP Wrapper, socks, Kerberos, secure_lib
COPS, TCP Wrapper,socks, Kerberos, secure_lib
TCP Wrapper, socks, Kerberos, S/Key, Token Generators
tftpCOPS, TCP Wrapper, socks, secure_lib
NIS

+ in passwd
ypset
ypserv
NIS Packets


COPS
TCP Wrapper, socks, secure_lib
TCP Wrapper, socks, secure_lib
socks, Security Extensions
NFS

exports
file handles


COPS, TCP Wrapper, socks, secure_lib
Security extensions, TCP Wrapper, socks, secure_lib
Passwords

guessing
cracking

guest
default


CRACK, passwd+, npasswd, password ageing, Token Generators, S/Key
CRACK, passwd+, npasswd, shadow passwords, password ageing, Token Generators, S/Key
password ageing, Crack
password ageing, Crack
Software

IFS
HOME
PATH
umask
error returns
signals


Secure Programming Methodologies
Secure Programming Methodologies
Secure Programming Methodologies
Secure Programming Methodologies
Secure Programming Methodologies
Secure Programming Methodologies
Monitoring

network
trojan horse


Kerberos, Token Generators, S/Key, DES, PEM, socks, Security Extensions
Tripwire,
Source CodeSecure Programming Methodologies
Protocol Weakness

spoofing


Secure Programming Methodologies, TCP Wrapper, secure_lib
Trojans

part of message
in programs


Secure Programming Methodologies
Tripwire
Spoofing MailPEM, PGP, MD2, MD4, MD5, DES, RIPEM, TIS/PEM
suid FilesSecure Programming Methodologies, COPS, Tripwire
World writeable files

files
devices



COPS
COPS
X WindowsTCP Wrapper, socks, secure_lib
Interaction
Configuration
COPS, TCP Wrapper, TAMU, secure_lib, Secure Programming Methodologies

5. Design of Security Enhancement for OpenVMS

This chapter details a design and implementation of the S/Key one time password system [KHW93] for Digital's OpenVMS operating system. OpenVMS is supplied with a number of standard security facilities including breakin detection and avoidance, a wide range of audit logging, and tailorable password control (such as lifetime and minimum length). However, OpenVMS is still susceptible to network monitoring attacks, possibly revealing passwords. There do exist products for OpenVMS that combat this type of threat, but these are generally registered products requiring initial purchase and a licence to run them. The author is unaware of any software which is provided free of charge in the public domain for performing this type of security enhancement to OpenVMS.

A simple solution to the problem where network monitoring may capture passwords is to use a one time password scheme. This may involve hardware/software solutions, but in general these are not as useful for providing wide spread use without requiring a sale of some form (e.g., the hardware devices). The optimal solution for enhancing the security of OpenVMS which can be used by anybody is to develop a totally software based solution. Such a solution already exists for Unix (S/Key), so an analysis of this was performed to determine its suitability for the OpenVMS environment. Section 5.1 details a description of the internal structure of the S/Key system.

A design was required for integrating the system into OpenVMS. This was met with a number of problems. OpenVMS provides a callout facility for LOGINOUT (the user authentication program) where a systems programmer may "replace" various facilities required by LOGINOUT (for example, the password authentication). The complexity of these callouts dictated that the S/Key system be integrated into OpenVMS using a prototype solution first to determine the viability of this extension.

The prototype design and implementation was completed as a two stage approach. The implementation of the first stage required porting the S/Key system from Unix to OpenVMS, and development of a prototype system that used S/Key. Upon completion of the first stage, a major security flaw was identified in this prototype. Further design work in the prototype was undertaken in an attempt to combat this security flaw. Both designs were implemented and tested. The design and implementation of the prototype system is detailed in Sections 5.2 and 5.3.

The demonstration of the modified prototype system indicated that the one time password system for OpenVMS would be an extremely useful addition to the security of OpenVMS. It would effectively combat the threat of network monitoring if it could be implemented in such a way that the identified security flaw in the prototype system could be overcome. This required full integration into the LOGINOUT authentication system for OpenVMS. This design and implementation is detailed in Section 5.4.

Section 5.5 identifies a number of areas where improvements in the final implementation could be made.

5.1. Description of S/Key

The S/Key system was developed at Bellcore in the United States. S/Key is a one time password system that is composed entirely of software. It has been designed mainly with Unix in mind (although it has been made available for MS-DOS and Macintosh as well).

S/Key uses the MD4 Message Digest algorithm [RFC1320] to provide the necessary security of the one time passwords. Each user is initialised into the system by providing an S/Key identification (which may be supplied by S/Key), and a secret password. These two items are concatenated together, and passed through MD4 to generate a 128 bit signature. This signature is collapsed to 64 bits, and forms the user's starting key. As the starting key has been processed through MD4, it is not possible to recreate all the information based upon the signature. Therefore, by storing the user's starting key and identifier (their ident) in a file, it is possible to verify the user by having them enter their secret password, and then verifying the starting key by the method detailed above.

The starting key is then processed through MD4, and the resulting 128 bit signature collapsed to 64 bits. This 64 bits is passed back into the MD4 function, and again the result collapsed to 64 bits. This continues until the desired range of passwords is reached. For example, if the user requires passwords 95 to 99 be generated, the one way function is performed 95 times, and then the results for the iteration number 95, 96, 97, 98, and 99 are displayed. These form the one time passwords. The next password to be used is the one with the highest unused number. In this example, that would be password number 99. Note that password number 99 was generated by passing password number 98 through MD4, and collapsing the output down to 64 bits. Therefore, it is not possible to determine password number 98 (the next password to be used) from knowledge of password number 99.

The 64 bit passwords are processed through a routine that maps set bit positions onto small English words. This allows for an easy display of the passwords for the users to interact with. For example, a password may look like:

SITU LEFT FLEW MALT MEL PUN

Passwords are displayed in this form, and are entered by users in this form. They are then converted back to the 64 bit representation for comparison.

The S/Key data file stores the identifiers for users, and the last valid S/Key one time password they successfully entered. When S/Key is used to verify a user, they are challenged for the password that is one less than the last successful one. This is entered by the user, converted to 64 bit binary, and then passed through the MD4 one way function. If the result of this matches the result stored in the file, then the user is considered to be valid. The entry for the user is updated to reflect the latest correctly entered password.

The S/Key data file must be protected from modification by unauthorised users. Therefore, the programs that access it for modification must be privileged in order for them to successfully update the file. On Unix, this is done via the SUID bit. On OpenVMS, this can be achieved by installing the programs with the SYSPRV privilege.

S/Key also provides a "secure" mode when reinitialising a user's S/Key entry. This does not require a user to generate a starting key by specifying a secret password (which may be monitored over an insecure network). Instead, the user should have previously generated a valid list of one time passwords while they are within their secure environment. From this point, the user may move to a new list by specifying the identifier, sequence number, and valid one time password. These items are entered into the system as being accurate without further checking. Only if these items are entered correctly can the next password to be used be verified successfully. The next password to be used will be the one with a sequence number one lower than the specified sequence number.

The S/Key system provides a simple yet effective solution to the problem of intruders monitoring a network for passwords.

5.2. Prototype Design

As such, there was very little design work in the initial stages to get S/Key to function under OpenVMS. A system already existed for Unix, so it was deemed to be prudent to remain as compatible with that as possible.

The design of the S/Key prototype integration was done entirely in user mode (did not require integration into the OpenVMS operating system). The program is designed to be called on every attempt at logging in to the system (including DECwindows, Internet access, and DECnet access). The design of the system is detailed below:

Upon entry, the program immediately establishes an exit handler. This exit handler checks the state of a global variable, and if not favourable will kill the process. This prevents any problems where a user may attempt to interrupt the execution of the program or the program terminates due to an abnormal condition;

If the terminal that the process is being created on does not have a physical device name, then this connection is not interactive and is a process related to the operation of other services such as DECwindows. These processes must be allowed to succeed, and the system can then proceed to check the next process when it logs in. DECwindows generally creates two processes when a new application is started. If the application is something like the calendar or clock, it will be unable to supply a password, and hence cannot be authenticated in this way. The global exit variable is set to a favourable state, and the program exited;

If the user is found within the S/Key system, they are prompted for the next valid password. They will be given a set timeout period to enter this password in;

If the correct password is entered successfully, then the program terminates favourably. Else, the process is killed;

If there are any errors returned from any system calls, the process is terminated.

This system is designed to be run by every process that logs in, and it will detect which processes require challenging by S/Key. Therefore, the command to run this may be placed in the system login file SYS$MANAGER:SYLOGIN.COM which is run by every process being created.

However, if an intruder captures an account and password for a privileged user, and then suddenly discovers on logging in that this system is in use, they may then attack the computer system by using other forms of connection, such as network connections. This may allow an intruder to edit the SYS$MANAGER:SYLOGIN.COM file and remove the command to execute the S/Key system. Network connections cannot be challenged by the S/Key system as they cannot enter the valid password.

Therefore, the prototype design was modified to help combat this type of attack. The following changes were designed:

If a user is challenged by the S/Key system and fails to provide the correct password, a log file is kept in the user's home directory, detailing the connect time and the source of the connection;

When a process is executing the S/Key challenge system, the system now checks for the mode of the process. If the mode is not INTERACTIVE (as opposed to BATCH, NETWORK, DETACHED, and so on), then a check is made to see if a non-empty log file exists for the user. If this is found, the process is terminated. The reasoning behind this is that if an intruder tries once using a captured account and login password but fails to supply the correct S/Key one time password, they may then attempt to defeat the system by accessing the computer via a non-interactive method such as FTP, DECnet COPY, and so on. If a non-empty log file exists for the user, these processes will be immediately terminated, thereby effectively preventing any further access to the system using the captured account and password;

The only mechanism for using the account now is to login interactively, and successfully pass the S/Key system. If the correct S/Key one time password is entered, then any existing log file is printed out on the screen for the user. This serves to warn the user that their login password may have been used by an unauthorised person.

Whilst this modified design provides slightly more protection against defeating the S/Key challenge system, it is still relatively easy to circumvent if knowledge of the existance of the system is gained by the intruder. They could for example, immediately disable the system prior to login interactively. Another mechanism might be that they capture more than one account and password, and as soon as one account is disabled (thus alerting the intruder to the presence of the S/Key challenge system), they may then enter the system using a second account to defeat the system.

These deficiencies are obviously significant, and render the security provided the existing prototype S/Key challenge system as insufficient for most needs. It may however serve as a "tripwire" or early warning device that security problems may exist on the computer system.

A design and implementation for how to integrate S/Key into OpenVMS in a way that it cannot be tampered with is given in Section 5.4.

5.3. Prototype Implementation

The implementation of S/Key for OpenVMS first required porting the existing S/Key code (written in the C language) to OpenVMS. A number of incompatibilities were encountered, and these required solutions to be developed to allow the system to function. A number of Unix specific functions were accessed by S/Key, and replacements for them required development under OpenVMS. The following problems and steps were required to port the code to OpenVMS:

The VAXC compiler [DEC89b] does not understand line breaks in a #if line. Therefore, some compiler functionality was required to handle this deficiency;

S/Key made use of the gethostname(), getuid(), getpwuid(), getpwnam(), and getopt() Unix system calls. These are not provided by OpenVMS and required replacement routines to be developed;

The function readpass() required replacement for OpenVMS;

OpenVMS generally does not support strict case matching and hence most information is supplied to programs in uppercase. However, for Unix compatibility, the VAXC language supports case matching and all arguments are supplied to the program in the exact case they were typed in. This caused problems with case matching that required resolution;

A bug existed in the existing S/Key system which caused an array overflow and variables to be destroyed. The bug fix has been communicated back to the authors of S/Key and has now been incorporated into their system;

Checking for a valid username and the privileges of a user required adapting to the OpenVMS environment;

The location of the data file required modification for OpenVMS;

OpenVMS requires different error and program return codes to the existing Unix system.

All extensions to the S/Key system for OpenVMS have been conditionally included into the code using the

#ifdef VAXC
#endif

conditional compilation directives. The VAXC symbol was chosen over the VMS symbol as some of the assumptions made for OpenVMS were only relevant to the VAX architecture. The author did not have access to the Alpha AXP architecture to perform development work for OpenVMS Alpha.

It is possible to initialise users other than yourself into the S/Key system. On Unix, this can only be performed by the root user (UID 0) which is the privileged user. However, on OpenVMS privileges are specified with a finer distinction than Unix, allowing partial privileges to be present, but not all privileges. Possession of any of the following privileges was deemed to be sufficient to indicate a privileged user:

SYSPRV may access objects via system protection

SETPRV may set any privilege bit

BYPASS may bypass object access controls

Whilst possession of other privileges may ultimately lead to the possession of any of those privileges above (such as LOG_IO, PHY_IO, CMKRNL), it is not necessary to check for the possession of these privileges as well. These privileges are designed to represent different access to the operating system and its objects. The fact that they can have a side effect of allowing other privileges to be obtained is of no consequence to this implementation.

Despite security flaws in the prototype design and implementation, it still demonstrates the usefulness of using S/Key to combat network monitoring, and improves security. For an intruder to combat this design, they must have already successfully captured a valid account and password of a privileged account. If the intruder is unaware of the presence of this S/Key prototype, then that captured account will be disabled when the intruder fails to supply the correct one time password. If the S/Key prototype is not in operation, then the security of the system has already been compromised severely. Ideally, the full integration of S/Key into the OpenVMS authentication procedures (LOGINOUT) is highly desired (see Section 5.4).

The modified S/Key code and the prototype implementation can be found in Appendix A.

5.4. Design and Implementation for Full Integration

To fully integrate the S/Key system securely into OpenVMS requires use of the new LOGINOUT Callouts facility [Cov90]. Only by using this mechanism can any confidence be gained that an attacker cannot modify or bypass the system.

OpenVMS has the concept of a second password for accounts. This is used for example, to provide two person authentication to a privileged account so that no single person can act without a second person also authenticating them into the system. Unfortunately, the S/Key system presently only supports a single password per account. Therefore, any accounts using two passwords are considered to be exempt from the S/Key system.

If the account does not possess any passwords, then no processing takes place and a success status is returned to LOGINOUT. Password prompting can only be performed on INTERACTIVE processes. Therefore, for any non-interactive processes, the S/Key callout returns a success status immediately, so that normal OpenVMS processing can continue. A SUBPROCESS of a parent cannot be prompted for a password, so like the previous situation, a success status is returned, and normal OpenVMS processing continued.

If any errors occur while processing the S/Key account or password, it is ignored, and the supplied password is considered to be an OpenVMS password. If subsequent verification of the OpenVMS password fails, the user is denied access to the system, and auditing and breakin detection is performed.

If the user is not found within the S/Key system, then they are given a standard OpenVMS password prompt. If they are found within the S/Key system, they are given an S/Key password prompt.

If the entered password fails to be authenticated using S/Key, it is checked using standard OpenVMS password checks. This is in case the user wishes to enter the system using their standard OpenVMS password (for example, if they are entering from within their secure environment).

As stated before, if the account uses two passwords, the second OpenVMS password is prompted for, and verified. By this stage, the first OpenVMS password will have been verified. Standard OpenVMS errors are passed back to LOGINOUT in reply to any problems with the authentication.

If any error occurs, the status is returned to LOGINOUT for processing. Otherwise, the status LGI$_SKIPRELATED is returned which indicates that the user has been fully authenticated and no further OpenVMS supplied authentication is required.

The DECwindows interface also calls the LGI$ICR_AUTHENTICATE callout to do user authentication. This is done after normal username and password prompting has been completed. Therefore, another callout is established which if called by the DECwindows initialisation routines, will disable all other callouts so that normal OpenVMS DECwindows authentication takes place. This is acceptable as a user normally logs in using DECwindows from within their secure environment.

Interfacing to the LOGINOUT Callouts requires careful programming and consideration. The module is built as a shareable image, and is invoked by LOGINOUT if a number of conditions are met. The commands to compile and link the module can be found in Section A.3.

The callout module requires a universal symbol LGI$LOGINOUT_CALLOUTS which defines a vector of pointers to callout routines. If a callout routine is not being used, its entry in the vector is 0. When a callout is executed, a number of standard parameters are passed to it. These are the argument vector and context. The argument vector is a vector of useful items including LOGINOUT callbacks (utility routines supplied by LOGINOUT to the callouts) plus LOGINOUT parameters (such as the username of the user trying to login), and a context which can be established at the start of processing.

The S/Key authentication callout requires a number of facilities from the argument vector. These include:

LGI$ICB_PASSWORD do password prompting and reading;

LGI$ICB_VALIDATE do OpenVMS password checking if the password is not authenticated by S/Key;

LGI$A_ICR_CREPRC_FLAGS determine if the process is interactive, invoked from a logged in process, or is a subprocess;

LGI$A_ICR_PWDCOUNT determine how many passwords this account has.

Once the module is linked as a shareable image, it must be placed in the SYS$SHARE: directory, and then installed into OpenVMS. Since LOGINOUT is a privileged program, OpenVMS dictates that it can only call installed images. This is done using the INSTALL command as demonstrated:

$ INSTALL REPLACE SYS$SHARE:LGI$CALLOUT_SKEY.EXE

A system-exec mode logical must then be defined that points to the filename. This can be done like:

$ DEFINE/SYSTEM/EXEC LGI$LOGINOUT_CALLOUTS LGI$CALLOUT_SKEY

Finally, to guard against subversion of the callouts by an intruder, a SYSGEN parameter is defined which dictates the number of callout modules that is expected. If this number differs from the definition, then the login fails. The sysgen parameter can be changed by:

$ RUN SYS$SYSTEM:SYSGEN

SYSGEN> SET LGI_CALLOUTS 1

SYSGEN> WRITE ACTIVE

SYSGEN> WRITE CURRENT

SYSGEN> EXIT

Note that these commands can only be executed by privileged users.

Testing the system showed a few more inconsistencies with S/Key and OpenVMS. When the one time password was returned from the LGI$ICB_PASSWORD call, it looked like:

FURYNASHNOBDEWPITBUG

instead of:

FURY NASH NOB DEW PIT BUG

Since it is almost impossible to determine where the spaces should have been placed, a design change was made to support OpenVMS. This design change was to place the "." character between S/Key words, rather than the space. The skey program was modified to reflect this change. Then internally to the callout, the "." characters are replaced by spaces prior to verifying the password. This has the effect of making the password valid to S/Key in the form it normally uses, even though it is entered in another form by the user. The resulting password now looks like:

FURY.NASH.NOB.DEW.PIT.BUG

A similar change was required to skeyinit when using the secure mode option. Using this option required the user to enter a one time password. Whilst it is possible to have the user enter the English words separated by spaces, it was deemed to be prudent to change skeyinit to accept the words with the "." character separating them for consistency with the rest of the package.

In addition to the above changes, the requirements of linking a shareable image meant that the normal VAXC run time library was not available. This dictated that the routine strftime() be developed, and linked in with the existing code.

Now when a user logs in to a system if the user is entered into the S/Key system, the interaction looks like:

Username: USER1
s/key 91 ko492804: ! enter FURY.NASH.NOB.DEW.PIT.BUG
Welcome to the OpenVMS system.
$
If the user is entered into the S/Key system, but chooses to use their OpenVMS password (for example, they are logging in from within their secure environment):

Username: USER1
s/key 90 ko492804: ! enter MYPASSWORD
Welcome to the OpenVMS system.
$

If a user is not entered into the S/Key system, then normal password prompting and validation will take place:

Username: USER3
Password: ! enter MYPASSWORD
Welcome to the OpenVMS system.
$

Any errors or failure to validate a password will cause the login to fail, and breakin detection to be performed by OpenVMS. This new design and implementation cannot be tampered with by an intruder. Using this new system, the passwords will remain secure provided the OpenVMS password is never transmitted across an open network, and only used internally on the secure network. If the user is required to login from an insecure network, they may optionally use a one time password to prevent replay attacks.

This greatly improves the security of OpenVMS as an intruder can no longer monitor the network and capture a valid username and password.

5.5. Future Work

More testing is required to determine how to protect the S/Key data file from modification by unauthorised means. Currently, all testing has been performed using a privileged account, which simplifies matters. Ultimately, the skey and skeyinit programs must be installed with privileges, so that access and modification of the S/Key data file is possible by non-privileged users, and the data file can then be better protected using standard OpenVMS file protections (such as permission bits [DEC89a], or ACLs [DEC88b]).

The date handling should be changed to conform to OpenVMS date formats, thereby removing the dependance on the routine strftime().

6. Conclusion

The problem of computer and network security is an extremely complex one. It has received increased attention over the past five years, and many tools and techniques have been developed to combat a wide range of threats. This report analyses a wide range of threats, and describes and categorises a collection of available tools and techniques used to combat those threats. Through this analysis, the awareness of the issues relating to computer security will be heightened.

This report examines a number of authentication techniques, looking at their strengths and weaknesses, and draws comparisons between them. This will aid security personnel in deciding the most appropriate authentication technique to meet their requirements.

A list of known threats to computer systems is detailed, and these threats are classified using standard criteria. Heightened understanding of the types and classes of computer threats will help systems administrators and programmers to avoid common mistakes in the future.

An examination of tools and techniques is performed. For each of the listed threats, the tools and techniques that can help combat that threat is determined. This is a comprehensive list, allowing a wide range of choice as to which solution (or solutions) best serve the needs of the computer community.

Following the examination, an enhancement in the security of OpenVMS is provided through the provision of a one time password scheme based upon S/Key. Further development work is required in this area.

6.1 Acknowledgement

A project of this scale could not have been completed effectively without the support and guidance of the author's supervisor, Dr. Jadwiga Indulska. Her suggestions, comments, and gentle steering were greatly appreciated.

7. Bibliography

[Alv90] De Alvare A. M., How Crackers Crack Passwords or What Passwords to Avoid, Proceedings of the UNIX Security Workshop II, Portland, August 1990.

[Arn93] Arnold N., UNIX Security: A Practical Tutorial, McGraw-Hill Inc., 1993.

[Aus93] Austen J., Fifth Computer Incident Handling Workshop, St. Louis, MO., August 1993.

[BB91] den Boer B. and Bosselaers A., An Attack on the Last Two Rounds of MD4, Proceedings of the Crypto'91 conference, Santa Barbara, August 1991.

[BB93] den Boer B. and Bosselaers A., Collisions for the compression function of MD5, Pre-proceedings of the EUROCRYPT 93 conference, Lofthus, May 1993.

[Bis87] Bishop M., How to Write a Setuid Program, ;login, Volume 12, Number 1, January/February 1987.

[Bis92a] Bishop M., Proactive Password Checking, Proceedings of the 4th Workshop on Computer Security Incident Handling, Denver, August 1992.

[Bis92b] Bishop M., README file for passwd+, anonymous ftp from dartmouth.edu, June 1992.

[BKS90] Baran F., Kaye H., and Suarez M., Security Breaches: Five Recent Incidents at Columbia University, Proceedings of the UNIX Security Workshop II, Portland, August 1990.

[BM91] Bellovin S. and Merritt, M., Limitations of the Kerberos Authentication System, Proceedings of the USENIX Winter 1991.

[Bra90] Brand R., Coping with the Threat of Computer Security Incidents: A Primer from Prevention through Recovery. CERT 0.6, June 1990.

[Bro93] Brown L, On Implementing Security Extensions to the TCP Transport Layer, Proceedings of the 16th Australian Computer Science Conference (ASCS-16), Brisbane, February 1993.

[CER92] Computer Emergency Response Team, Internet Security for UNIX System Administrators, Presented at AARNet Networkshop, December 1992.

[CER93] Computer Emergency Response Team Advisory 93:14, Internet Security Scanner (ISS), September 1993.

[Che92] Cheswick W.. An evening with Berferd in which a Cracker is Lured, Endured, and Studied, Proceedings of the Winter USENIX Conference, San Francisco, January 1992.

[CLS91] Caelli W., Longley D., and Shain M., Information Security Handbook, Stockton Press, 1991.

[Cly93] Clyde R., DECnet Security (Not Necessarily an Oxymoron), Computers and Security, March 1993.

[Coh92] Cohen F., A Formal Definition of Computer Worms and Some Related Results, Computers and Security, Volume 11, Number 7, November 1992.

[Cov90] Covert J., Functional Specification for Callouts for LOGINOUT & DECnet Session, Version T1.0.0, Digital Equipment Corporation, July 1990.

[Cur90] Curry D., Improving the Security of your UNIX System, ITSTD-721-FR-90-21, SRI International, April 1990.

[DEC88a] Guide to DECnet-VAX Networking Version 5.0, Digital Equipment Corporation, April 1988.

[DEC88b] VMS Access Control List Editor Manual Version 5.0, Digital Equipment Corporation, April 1988.

[DEC89a] Guide to VMS System Security Version 5.2, Digital Equipment Corporation, June 1989.

[DEC89b] VAX C Run-Time Library Reference Manual Version 3.1, Digital Equipment Corporation, December 1989.

[DEC90] VMS Authorize Utility Manual Version 5.4, Digital Equipment Corporation, August 1990.

[Din90] Dinkel C., Secure Data Network System (SDNS) Network, Transport and Message Security Protocols, NIST, NISTIR-90/4250, March 1990.

[Edw90] Edwards B., How to Survive a Computer Disaster, Proceedings of the DECUS Symposium, August 1990.

[Ell92] Ellison C., RESULTS: challenge login devices, Usenet newsgroup sci.crypt, 6 October 1992.

[Far91a] Farmer D., README.1 file from COPS system, anonymous ftp from cert.org, November 1991.

[Far91b] Farrow R., Unix System Security: How to Protect your Data and Prevent Intruders, Addison-Wesley, April 1991.

[FIP77] Federal Information Processing Standards Publication 46, Data Encryption Standard, National Bureau of Standards, U.S. Department of Commerce, January 1977.

[Goa92] Goatley H., Supervisor Reference Guide, anonymous ftp from ftp.spc.edu, October 1992.

[Gro93] Grottola M., The UNIX Audit: Using UNIX to Audit UNIX, McGraw-Hill Inc., 1993.

[GS91] Garfinkel S. and Spafford G., Practical UNIX Security, O'Reilly and Associates, Inc., 1991.

[Hei90] Heirtzler J., shadow.howto file from shadow system, anonymous ftp from csc2.anu.edu.au, April 1990.

[Hey93a] Van Heyningen M., RIPEM Frequently Asked Questions, Usenet newsgroup alt.security.ripem, 31 March 1993.

[Hey93b] Van Heyningen M., RIPEM Frequently Noted Vulnerabilities, Usenet newsgroup alt.security.ripem, 31 March 1993.

[Hoo90] Hoover C., README file from npasswd system, anonymous ftp from ftp.cc.utexas.edu, March 1990.

[HY92] Harn L. and Yang S., Group Oriented Undeniable Signature Schemes without the Assistance of a Mutually Trusted Party, Proceedings AUSCRYPT '92, Gold Coast, December 1992.

[IBM89] Virtual Machine/Directory Maintenance - Operation and Use, Release 4, International Business Machines,1989.

[ISO92] International Standards Organisation ISO 9594-8: The Directory: Authentication Framework, 1992 (also known as CCITT Recommendation X.509).

[JM91] Janson P and Molva R., Security in Open Networks and Distributed Systems, Computer Networks and ISDN Systems, Volume 22, Number 5, October 1991.

[KC90] Kaplan R., and Clyde R., Viruses, Worms, and Trojan Horses - Part VI: The War Continues, Proceedings DECUS Fall 1990, Las Vegas, 1990.

[KCS90] Kohl J., Neuman B., and Steiner J., The Kerberos Network Authentication Service, MIT Project Athena, Version 5 Draft 3, October 1990.

[KHW93] Karn P., Haller N., and Walden J., S/Key One Time Password System, anonymous ftp from thumper.bellcore.com, July 1993.

[KK92] Koblas D. and Koblas M., SOCKS, Proceedings of the USENIX Security Symposium, 1992.

[Kle90] Klein D., "Foiling the Cracker": A Survey of, and Improvements to, Password Security, Proceedings of the UNIX Security Workshop II, Portland, August 1990.

[KS92] Kim G. and Spafford E., README file from Tripwire system, anonymous ftp from cert.org, November 1992.

[Kur90] Kuras J., An Expert Systems Approach to Security Inspection of UNIX, Proceedings of the UNIX Security Workshop II, Portland, August 1990.

[LAB92] Lampson B., Abadi M., Burrows M., and Wobber E., Authentication in Distributed Systems: Theory and Practice, acm Transactions on Computer Systems, November 1992.

[Lau92] Laun, R., Asymmetric User Authentication, Computers and Security, Volume 11, Number 2, April 1992.

[Law93] Lawrence L., Digital Signatures - Explanation and Usage, Computers and Security, Volume 12, Number 3, May 1993.

[LeF92] LeFebvre W., Restricting Network Access to System Daemons under SunOS, securelib system, anonymous ftp from eecs.nwu.edu, 1992.

[LS93] Longstaff T. and Schultz E., Beyond Preliminary Analysis of the WANK and OILZ Worms: A Case Study of Malicious Code, Computers and Security, Volume 12, Number 1, February 1993.

[Mad93] Madsen J., World record in password checking, Usenet newsgroup alt.security, 1 September 1993.

[MLJ92] McCanne S., Leres C., and Jacobson V., README file from tcpdump system, anonymous ftp from ftp.ee.lbl.gov, May 1992.

[Mor90] Moraes M., YP is not secure, Security Digest, Volume 3, Issue 12, May 1990.

[MP92] Mui L. and Pearce E., X Window System Administrator's Guide, O'Reilleys & Associates Inc., 1992.

[Muf92] Muffett A., "Crack Version 4.1" A Sensible Password Checker for Unix, anonymous ftp from cert.org, March 1992.

[Ney92] Ney S., README file from TAP system, anonymous ftp from ftp.cs.tu-berlin.de, March 1992.

[OSI92] The OSI Security Package, OSISEC Users Manual V0.2, July 1992.

[RFC783] Sollins K., The TFTP Protocol (Revision 2), Network Working Group, RFC783, June 1981.

[RFC1094] Sun Microsystems Inc., Network File System Protocol Specification, Network Working Group, RFC1094, March 1989.

[RFC1319] Kaliski B., The MD2 Message-Digest Algorithm, Network Working Group, RFC1319, April 1992.

[RFC1320] Rivest R., The MD4 Message-Digest Algorithm, Network Working Group, RFC1320, April 1992.

[RFC1321] Rivest R., The MD5 Message-Digest Algorithm, Network Working Group, RFC1321, April 1992.

[RFC1421] Linn J., Privacy Enhancement for Internet Electronic Mail: Part I: Message Encryption and Authentication Procedures, Network Working Group, RFC1421, February 1993.

[RFC1422] Kent S., Privacy Enhancement for Internet Electronic Mail: Part II: Certificate-Based Key Management, Network Working Group, RFC1422, February 1993.

[RFC1423] Balenson D., Privacy Enhancement for Internet Electronic Mail: Part III: Algorithms, Modes, and Identifiers, Network Working Group, RFC1423, February 1993.

[RFC1424] Kaliski B., Privacy Enhancement for Internet Electronic Mail: Part IV: Key Certification and Related Services, Network Working Group, RFC1424, February 1993.

[RSA78] Rivest R., Shamir A., and Adleman L., A Method for Obtaining Digital Signatures and Public-key Cryptosystems, Communications of the ACM, February 1978.

[SER93] Security Emergency Response Team Advisory 93.04, Guidelines for Developing a Sensible Password Policy, June 1993.

[Sho93] Shostack A., NIS Issues, Usenet Newsgroup comp.security.misc, 4 August 1993.

[Smi93] Smith D., Public Key Cryptosystems, Certificates, and Certification Authorities, Security Emergency Response Team, October 1993.

[Spa88] Spafford, E., The Internet Work Program: An Analysis, Technical Report CSD-TR-823, Department of Computer Science, Purdue University, November 1988.

[Spa92] Spafford E., OPUS: Preventing Weak Password Choices, Computers and Security, May 1992.

[Spa93] Spafford E., NIS Issues, Usenet Newsgroup comp.scurity.misc, 4 August 1993.

[SSH93] Safford D., Schales D., and Hess D., Texas A & M Network Security Package Overview, anonymous ftp from sc.tamu.edu, July 1993.

[Ste90] Stevens W., UNIX Network Programming, Prentice Hall, 1990.

[Sto89] Stoll C., The Cuckoo's Egg, Doubleday, 1989.

[Sun90a] System and Network Administration, SUN Microsystems, Revision A, March 1990.

[Sun90b] SunOS Reference Manual, Volume 1, SUN Microsystems, Revision A, March 1990.

[Sun90c] SunOS Reference Manual, Volume 2, SUN Microsystems, Revision A, March 1990.

[Tan89] Tanenbaum A., Computer Networks, Prentice-Hall International Inc.1989.

[TAP90] Tardo J., Alagappan K., and Pitkin R., Public Key Authentication using Internet Certificates, Proceedings of the UNIX Security Workshop II, Portland, August 1990.

[TIS93] TIS/PEM FAQ (Frequently Asked Questions), anonymous ftp from ftp.tis.com, June 1993.

[UCL92] Research Report, Department of Computer Science, University College London, 1992.

[Ven92] Venema W., BLURB file from TCP Wrapper system, anonymous ftp from cert.org, June 1992.

[Zim92] Zimmermann P., README file from PGP system, anonymous ftp from ghost.dsi.unimi.it, November 1992.

A. Appendix A - Sample Source Code

This Appendix contains the source code used to implement the S/Key system under OpenVMS. It contains the original files from the S/Key system, plus additional files supplied by the author. Any changes or additions are marked with change bars in the left margin.

A.1. Header Files

A.1.1. challenge.h

/*
**  Define the log file location   
*/
#define LOG_FILE        "sys$login:challenge.log"
#define VERBOSE_LOG     "sys$login:challenge_run.log"

/*
**  Define if debugging code is required
*/
/* #define DEBUG        /* if debugging the code */

/*
**  Define if verbose mode is required (for debugging)
*/
/* #define VERBOSE      /* if this is low level debugging */

/*
**  Define if you don't want the process really killed
**  (for debugging only!!!!)
*/
/* #define NO_KILL      /* don't kill the process */

/*
** Define some boolean types for our use
*/
#define BOOLEAN enum{ TRUE, FALSE};
/*
**  Maximum length of input buffer from terminal
*/
#define ANSWER_LEN      128

/*
**  Size of buffer from log file
*/
#define BUFFER_LEN      132

/*
**  Size of buffer for time strings
*/
#define TIME_BUF_LEN    128

/*
**  Size of buffer to return DVI info in
*/
#define DVI_BUF_LEN     128

/*
**  Size of buffer to return JPI info in
*/
#define JPI_BUF_LEN     128

A.1.2. md4.h

#ifdef  __STDC__
#define __ARGS(X) X     /* For ANSI C */
#else
#define __ARGS(X) ()
#endif
 
/*
 *
 * md4.h -- Header file for implementation of MD4 Message Digest Algorithm
 * Updated: 2/13/90 by Ronald L. Rivest
 * (C) 1990 RSA Data Security, Inc.
 * Reformatted and de-linted - 2/12/91 Phil Karn
 */
  
/* MDstruct is the data structure for a message digest computation. */
typedef struct {
    unsigned long buffer[4];/* Holds 4-word result of MD computation */
    unsigned char count[8]; /* Number of bits processed so far */
    unsigned int done;      /* Nonzero means MD computation finished */
} MDstruct, *MDptr;

/* MDbegin(MD)
 * Input: MD -- an MDptr
 * Initialize the MDstruct prepatory to doing a message digest computation.
 */
extern void MDbegin __ARGS((MDptr MDp));

/* MDupdate(MD,X,count)
 * Input: MD -- an MDptr
 *        X -- a pointer to an array of unsigned characters.
 *        count -- the number of bits of X to use (an unsigned int).
 * Updates MD using the first ``count'' bits of X.
 * The array pointed to by X is not modified.
 * If count is not a multiple of 8, MDupdate uses high bits of last byte.
 * This is the basic input routine for a user.
 * The routine terminates the MD computation when count < 512, so
 * every MD computation should end with one call to MDupdate with a
 * count less than 512.  Zero is OK for a count.
 */
extern void MDupdate __ARGS((MDptr MDp,unsigned char *X,unsigned int count));
/* MDprint(MD)
 * Input: MD -- an MDptr
 * Prints message digest buffer MD as 32 hexadecimal digits.
 * Order is from low-order byte of buffer[0] to high-order byte of buffer[3].
 * Each byte is printed with high-order hexadecimal digit first.
 */
extern void MDprint __ARGS((MDptr MDp));
/* End of md4.h */

A.1.3. skey.h

#if     defined(__TURBOC__) || defined(__STDC__) || defined(LATTICE)
#define ANSIPROTO       1
#endif

#ifndef __ARGS
#ifdef  ANSIPROTO
#define __ARGS(x)       x
#else
#define __ARGS(x)       ()
#endif
#endif

/* Server-side data structure for reading keys file during login */
struct skey {
    FILE *keyfile;
    char buf[256];
    char *logname;
    int n;
    char *seed;
    char *val;
    long    recstart; /*needed so reread of buffer is efficient*/


};

/* Client-side structure for scanning data stream for challenge */
struct mc {
    char buf[256];
    int skip;
    int cnt;
};

void f __ARGS((char *x));
int keycrunch __ARGS((char *result,char *seed,char *passwd));
char *btoe __ARGS((char *engout,char *c));
char *put8 __ARGS((char *out,char *s));
int etob __ARGS((char *out,char *e));
void rip __ARGS((char *buf));
int skeychallenge __ARGS((struct skey *mp,char *name));
int skeylookup __ARGS((struct skey *mp,char *name));
int skeyverify __ARGS((struct skey *mp,char *response));

A.2. Source Code Files

A.2.1. gethostname.c

#include 
#include 
#include 

#ifdef NOT_USED
main()
{
char    name[ 8];

gethostname( name, 8);
printf( "hostname is %sn", name);
}
#endif

int gethostname( name, namelen)
char    *name;
int     namelen;
{
int                 ret_length;
struct
{
    unsigned short  len;
    unsigned short  item;
    char            *buffer;
    int             *ret_len;
    int             terminator;
} item_list = { namelen, SYI$_NODENAME, name, &ret_length, 0};
unsigned int        status;
struct
{
    unsigned int    status;
    unsigned short  ret_length;
    unsigned short  padding;
} iosb;
char                *cp;

status = sys$getsyi( 0, 0, 0, &item_list, &iosb, 0, 0);
cp = name;
while ( *cp != '')
{
    *cp = ( char) tolower( ( int) *cp);

{
    return( -1);
}
return( 0);
}   

A.2.1. getskeyprompt.c

/*   get prompt code for S/KEY Authentication.  S/KEY is a trademark
 *   of Bellcore.
 *
 *   Mink is the former name of the S/KEY authentication system.
 *   Many references for mink  may still be found in this program.   */

#include 
#include 
#ifdef VAXC
#include 
#include 
#else
#include 
#include 
#endif
#include 
#include 
#include "skey.h"

#ifdef VAXC
#define KEYFILE "SYS$MANAGER:SKEYKEYS.DAT"
#else
#define KEYFILE "/etc/skeykeys"
#endif

char *skipspace();
int skeylookup __ARGS((struct skey *mp,char *name));

/* Issue a skey challenge for user 'name'. If successful,
 * fill in the caller's skey structure and return 0. If unsuccessful
 * (e.g., if name is unknown) return -1.
 *  
 * The file read/write pointer is left at the start of the
 * record.
 */ 
int
getskeyprompt(mp,name,prompt)
struct skey *mp;
char *name;
char *prompt;
{   
    int rval;
   
    sevenbit(name);
    rval = skeylookup(mp,name);
    strcpy(prompt,"s/key 55 latour1n");
    switch(rval){
    case -1:        /* File error */
            return -1;
    case 0:         /* Lookup succeeded, return challenge */
            sprintf(prompt,"s/key %d %sn",mp->n - 1,mp->seed);
            return 0;
    case 1:         /* User not found */
            fclose(mp->keyfile);
            return -1;
    }
    return -1;      /* Can't happen */
}      

A.2.3. md4.c

/*
 * md4.c -- Implementation of MD4 Message Digest Algorithm
 * Updated: 2/16/90 by Ronald L. Rivest
 * (C) 1990 RSA Data Security, Inc.
 *  
 * Portability nits fixed and reformatted - 2/12/91 Phil Karn
 */ 
  
/*
 * To use MD4:
 *   -- Include md4.h in your program
 *   -- Declare an MDstruct MD to hold the state of the digest computation.
 *   -- Initialize MD using MDbegin(&MD)
 *   -- For each full block (64 bytes) X you wish to process, call
 *          MDupdate(&MD,X,512)
 *      (512 is the number of bits in a full block.)
 *   -- For the last block (less than 64 bytes) you wish to process,
 *          MDupdate(&MD,X,n)
 *      where n is the number of bits in the partial block. A partial
 *      block terminates the computation, so every MD computation should
 *      terminate by processing a partial block, even if it has n = 0.
 *   -- The message digest is available in MD.buffer[0] ... MD.buffer[3].
 *      (Least-significant byte of each word should be output first.)
 *   -- You can print out the digest using MDprint(&MD)
 */
 
/* Implementation notes:
 * This implementation assumes that longs are 32-bit quantities.
 * If the machine stores the least-significant byte of an long in the
 * least-addressed byte (eg., VAX and 8086), then LOWBYTEFIRST should be
 * set to TRUE.  Otherwise (eg., SUNS), LOWBYTEFIRST should be set to
 * FALSE.  Note that on machines with LOWBYTEFIRST FALSE the routine
 * MDupdate modifies has a side-effect on its input array (the order of bytes
 * in each word are reversed).  If this is undesired a call to MDreverse(X) can
 * reverse the bytes of X back into order after each call to MDupdate.
 */
#define TRUE  1
#define FALSE 0
 
#ifdef VAXC             /* VAXC hates splitting lines in #if statements */
#define LOWBYTEFIRST TRUE
#else
#if (defined(__MSDOS__) || defined(MPU8086) || defined(MPU8080) 
 || defined(vax) || defined (MIPSEL))
#define LOWBYTEFIRST TRUE       /* Low order bytes are first in memory */
#else                   /* Almost all other machines are big-endian */
#define LOWBYTEFIRST FALSE
#endif
#endif


/* Compile-time includes */
#include 
#include "md4.h"

/* Compile-time declarations of MD4 ``magic constants'' */
#define I0  0x67452301       /* Initial values for MD buffer */
#define I1  0xefcdab89
#define I2  0x98badcfe
#define I3  0x10325476
#define C2  013240474631     /* round 2 constant = sqrt(2) in octal */
#define C3  015666365641     /* round 3 constant = sqrt(3) in octal */
/* C2 and C3 are from Knuth, The Art of Programming, Volume 2
 * (Seminumerical Algorithms), Second Edition (1981), Addison-Wesley.
 * Table 2, page 660.
 */
#define fs1  3               /* round 1 shift amounts */
#define fs2  7
#define fs3 11
#define fs4 19
#define gs1  3               /* round 2 shift amounts */
#define gs2  5
#define gs3  9
#define gs4 13
#define hs1  3               /* round 3 shift amounts */
#define hs2  9
#define hs3 11
#define hs4 15


/* Compile-time macro declarations for MD4.
 * Note: The ``rot'' operator uses the variable ``tmp''.
 * It assumes tmp is declared as unsigned long, so that the >>
 * operator will shift in zeros rather than extending the sign bit.
 */
#define f(X,Y,Z)             ((X&Y) | ((~X)&Z))
#define g(X,Y,Z)             ((X&Y) | (X&Z) | (Y&Z))
#define h(X,Y,Z)             (X^Y^Z)
#define rot(X,S)             (tmp=X,(tmp<>(32-S)))
#define ff(A,B,C,D,i,s)      A = rot((A + f(B,C,D) + X[i]),s)
#define gg(A,B,C,D,i,s)      A = rot((A + g(B,C,D) + X[i] + C2),s)
#define hh(A,B,C,D,i,s)      A = rot((A + h(B,C,D) + X[i] + C3),s)
 
void MDreverse __ARGS((unsigned long *X));
 
/* MDprint(MDp)
 * Print message digest buffer MDp as 32 hexadecimal digits.
 * Order is from low-order byte of buffer[0] to high-order byte of buffer[3].
 * Each byte is printed with high-order hexadecimal digit first.
 * This is a user-callable routine.
 */
void
MDprint(MDp)
MDptr MDp;
{
    int i,j;
   
    for(i=0;i<4;i++)
            for(j=0;j<32;j=j+8)
                    printf("%02lx",(MDp->buffer[i]>>j) & 0xFF);
}      

/* MDbegin(MDp)
 * Initialize message digest buffer MDp.
 * This is a user-callable routine.
 */
void
MDbegin(MDp)
MDptr MDp;
{
    int i;
   
    MDp->buffer[0] = I0;
    MDp->buffer[1] = I1;
    MDp->buffer[2] = I2;
    MDp->buffer[3] = I3;
    for(i=0;i<8;i++)
            MDp->count[i] = 0;
    MDp->done = 0;
}

/* MDreverse(X)
 * Reverse the byte-ordering of every long in X.
 * Assumes X is an array of 16 longs.
 * The macro revx reverses the byte-ordering of the next word of X.
 */
#define revx { t = (*X << 16) | (*X >> 16); 
           *X++ = ((t & 0xFF00FF00) >> 8) | ((t & 0x00FF00FF) << 8); }
void
MDreverse(X)
unsigned long *X;
{
    register unsigned long t;

    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
    revx;
}

/* MDblock(MDp,X)
 * Update message digest buffer MDp->buffer using 16-word data block X.
 * Assumes all 16 words of X are full of data.
 * Does not update MDp->count.
 * This routine is not user-callable.
 */
static void
MDblock(MDp,X)
MDptr MDp;
unsigned long *X;
{
    register unsigned long tmp, A, B, C, D;
   
#if LOWBYTEFIRST == FALSE
    MDreverse(X);
#endif 
    A = MDp->buffer[0];
    B = MDp->buffer[1];
    C = MDp->buffer[2];
    D = MDp->buffer[3];
    /* Update the message digest buffer */
    ff(A,B,C,D,0,fs1); /* Round 1 */
    ff(D,A,B,C,1,fs2);
    ff(C,D,A,B,2,fs3);
    ff(B,C,D,A,3,fs4);
    ff(A,B,C,D,4,fs1);
    ff(D,A,B,C,5,fs2);
    ff(C,D,A,B,6,fs3);
    ff(B,C,D,A,7,fs4);
    ff(A,B,C,D,8,fs1);
    ff(D,A,B,C,9,fs2);
    ff(C,D,A,B,10,fs3);
    ff(B,C,D,A,11,fs4);
    ff(A,B,C,D,12,fs1);
    ff(D,A,B,C,13,fs2);
    ff(C,D,A,B,14,fs3);
    ff(B,C,D,A,15,fs4);
    gg(A,B,C,D,0,gs1); /* Round 2 */
    gg(D,A,B,C,4,gs2);
    gg(C,D,A,B,8,gs3);
    gg(B,C,D,A,12,gs4);
    gg(A,B,C,D,1,gs1);
    gg(D,A,B,C,5,gs2);
    gg(C,D,A,B,9,gs3);
    gg(B,C,D,A,13,gs4);
    gg(A,B,C,D,2,gs1);
    gg(D,A,B,C,6,gs2);
    gg(C,D,A,B,10,gs3);
    gg(B,C,D,A,14,gs4);
    gg(A,B,C,D,3,gs1);
    gg(D,A,B,C,7,gs2);
    gg(C,D,A,B,11,gs3);
    gg(B,C,D,A,15,gs4);
    hh(A,B,C,D,0,hs1); /* Round 3 */
    hh(D,A,B,C,8,hs2);
    hh(C,D,A,B,4,hs3);
    hh(B,C,D,A,12,hs4);
    hh(A,B,C,D,2,hs1);
    hh(D,A,B,C,10,hs2);
    hh(C,D,A,B,6,hs3);
    hh(B,C,D,A,14,hs4);
    hh(A,B,C,D,1,hs1);
    hh(D,A,B,C,9,hs2);
    hh(C,D,A,B,5,hs3);
    hh(B,C,D,A,13,hs4);
    hh(A,B,C,D,3,hs1);
    hh(D,A,B,C,11,hs2);
    hh(C,D,A,B,7,hs3);
    hh(B,C,D,A,15,hs4);
    MDp->buffer[0] += A;
    MDp->buffer[1] += B;
    MDp->buffer[2] += C;
    MDp->buffer[3] += D;
}

/* MDupdate(MDp,X,count)
 * Input: MDp -- an MDptr
 *        X -- a pointer to an array of unsigned characters.
 *        count -- the number of bits of X to use.
 *                 (if not a multiple of 8, uses high bits of last byte.)
 * Update MDp using the number of bits of X given by count.
 * This is the basic input routine for an MD4 user.
 * The routine completes the MD computation when count < 512, so
 * every MD computation should end with one call to MDupdate with a
 * count less than 512.  A call with count 0 will be ignored if the
 * MD has already been terminated (done != 0), so an extra call with count
 * 0 can be given as a ``courtesy close'' to force termination if desired.
 */
void
MDupdate(MDp,X,count)
MDptr MDp;
unsigned char *X;
unsigned int count;
{
    int i,bit,byte,mask;
    unsigned long tmp;
    unsigned char XX[64];
    unsigned char *p;
   
    /* return with no error if this is a courtesy close with count
     * zero and MDp->done is true.
     */
    if(count == 0 && MDp->done)
            return;
    /* check to see if MD is already done and report error */
    if(MDp->done){
            printf("nError: MDupdate MD already done.");
            return;
    }
    /* Add count to MDp->count */
    tmp = count;
    p = MDp->count;
    while(tmp){
            tmp += *p;
            *p++ = tmp;
            tmp = tmp >> 8;
    }
    /* Process data */
    if(count == 512){
            /* Full block of data to handle */
            MDblock(MDp,(unsigned long *)X);
    } else if(count > 512){

            /* Check for count too large */
            printf("nError: MDupdate called with illegal count value
%ld.",
count);
            return;
    } else {
            /* partial block -- must be last block so finish up
             * Find out how many bytes and residual bits there are
             */
            byte = count >> 3;
            bit =  count & 7;
            /* Copy X into XX since we need to modify it */
            for(i=0;i<=byte;i++)
                    XX[i] = X[i];
            for(i=byte+1;i<64;i++)
                    XX[i] = 0;
            /* Add padding '1' bit and low-order zeros in last byte */
            mask = 1 << (7 - bit);
            XX[byte] = (XX[byte] | mask) & ~( mask - 1);
            /* If room for bit count, finish up with this block */
            if(byte <= 55){
                    for(i=0;i<8;i++)
                            XX[56+i] = MDp->count[i];
                    MDblock(MDp,(unsigned long *)XX);
            } else {
                    /* need to do two blocks to finish up */
                    MDblock(MDp,(unsigned long *)XX);
                    for(i=0;i<56;i++)
                            XX[i] = 0;
                    for(i=0;i<8;i++)
                            XX[56+i] = MDp->count[i];
                    MDblock(MDp,(unsigned long *)XX);
            }
    /* Set flag saying we're done with MD computation */
    MDp->done = 1;
    }
}
/* End of md4.c */

A.2.4. put.c

#include 
#include 
#include 
#include 
#include "skey.h"

static unsigned long extract __ARGS((char *s,int start,int length));
static void standard __ARGS((char *word));
static void insert __ARGS((char *s, int x, int start, int length));
static int wsrch __ARGS((char *w,int low,int high));

/* Dictionary for integer-word translations */
char Wp[2048][4] = {
"A",
"ABE",
"ACE",
"ACT",
"AD",
"ADA",
"ADD",
"AGO",
"AID",
"AIM",
"AIR",
"ALL",
"ALP",
"AM",
"AMY",
 
... Many words deleted here for brevity.  Consult the original source code for
t
he entire contents ...

"YALE",
"YANG",
"YANK",
"YARD",
"YARN",
"YAWL",
"YAWN",
"YEAH",
"YEAR",
"YELL",
"YOGA",
"YOKE"
};   
 
/* Encode 8 bytes in 'c' as a string of English words.
 * Returns a pointer to a static buffer
 */
char *
btoe(engout,c)
char *c, *engout;
{
    char cp[9];     /* add in room for the parity 2 bits*/
    int p,i ;
    char    seperator[ 2];

    engout[0] = '';
    memcpy(cp, c,8);
    /* compute parity */
    for(p = 0,i = 0; i < 64;i += 2)
            p += extract(cp,i,2);

#ifdef VAXC
    strncpy( seperator, ".", 2);
#else
    strncpy( seperator, " ", 2);
#endif
    cp[8] = (char)p << 6;
    strncat(engout,&Wp[extract(cp, 0,11)][0],4);
    strcat(engout, seperator);
    strncat(engout,&Wp[extract(cp,11,11)][0],4);
    strcat(engout, seperator);
    strncat(engout,&Wp[extract(cp,22,11)][0],4);
    strcat(engout, seperator);
    strncat(engout,&Wp[extract(cp,33,11)][0],4);
    strcat(engout, seperator);
    strncat(engout,&Wp[extract(cp,44,11)][0],4);
    strcat(engout, seperator);
    strncat(engout,&Wp[extract(cp,55,11)][0],4);
#ifdef  notdef
    printf("engout is %snr",engout);
#endif
    return(engout);
}

/* convert English to binary
 * returns 1 OK - all good words and parity is OK
 *         0 word not in data base
 *        -1 badly formed in put ie > 4 char word
 *        -2 words OK but parity is wrong
 */
int
etob(out, e)
char *out;
char *e;
{      
    char *word;
    int i, p, v,l, low,high;
    char b[9];
    char input[36];

    if(e == NULL)
            return -1;

    strncpy(input,e,sizeof(input));
    memset(b, 0, sizeof(b));
    memset(out, 0, 8);
    for(i=0,p=0;i<6;i++,p+=11){
            if((word = strtok(i == 0 ? input : NULL," ")) == NULL)
                    return -1;
            l = strlen(word);
            if(l > 4 || l < 1){
                    return -1;
            } else if(l < 4){
                    low = 0;
                    high = 570;
            } else {
                    low = 571;
                    high = 2047;
            }
            standard(word);
            if( (v = wsrch(word,low,high)) < 0 )
                    return 0;
            insert(b,v,p,11);
    }

    /* now check the parity of what we got */
    for(p = 0, i = 0; i < 64; i +=2)
            p += extract(b, i, 2);

    if( (p & 3) != extract(b, 64,2) )
            return -2;

    memcpy(out,b,8);

    return 1;
}
/* Display 8 bytes as a series of 16-bit hex digits */
char *
put8(out,s)
char *out;
char *s;
{      
    sprintf(out,"%02X%02X %02X%02X %02X%02X %02X%02X",
            s[0] & 0xff,s[1] & 0xff,s[2] & 0xff,
            s[3] & 0xff,s[4] & 0xff,s[5] & 0xff,
            s[6] & 0xff,s[7] & 0xff);
    return out;
}
#ifdef  notdef
/* Encode 8 bytes in 'cp' as stream of ascii letters.
 * Provided as a possible alternative to btoe()
 */
char *
btoc(cp)
char *cp;
{
    int i;
    static char out[31];

    /* code out put by characters 6 bits each added to 0x21 (!)*/
    for(i=0;i <= 10;i++){
            /* last one is only 4 bits not 6*/
            out[i] = '!'+ extract(cp,6*i,i >= 10 ? 4:6);
    }
    out[i] = '';
    return(out);
}
#endif
 
/* Internal subroutines for word encoding/decoding */

/* Dictionary binary search */
static int
wsrch(w,low,high)
char *w;
int low, high;
{
    int i,j;

    for(;;){
            i = (low + high)/2;
            if((j = strncmp(w,Wp[i],4)) == 0)
                    return i;       /* Found it */
            if(high == low+1){
                    /* Avoid effects of integer truncation in /2 */
                    if(strncmp(w,Wp[high],4) == 0)
                            return high;
                    else
                            return -1;
            }
            if(low >= high)
                    return -1;      /* I don't *think* this can happen...*/
            if(j < 0)
                    high = i;       /* Search lower half */
            else
                    low = i;        /* Search upper half */
    }
}
static void
insert(s, x, start, length)
char *s;
int x; 
int  start, length;
{
    unsigned char cl;
    unsigned char cc;
    unsigned char cr;
    unsigned long y;
    int shift;

    assert(length <= 11);
    assert(start >= 0);
    assert(length >= 0);
    assert(start +length <= 66);

    shift = ((8  -(( start + length) % 8))%8);
    y = (long) x << shift;
    cl = (y >> 16) & 0xff;
    cc = (y >> 8) & 0xff;
    cr = y & 0xff;
    if(shift + length > 16){
            s[start /8] |= cl;
            s[start/8 +1] |= cc;
            s[start/8 +2] |= cr;
    } else if(shift +length > 8){
            s[start/8] |= cc;
            s[start/8 + 1] |= cr;
    } else {
            s[start/8] |= cr;
    }
}

static void
standard(word)
register char *word;
{
    while(*word){
            if(!isascii(*word))
                    break;
            if(islower(*word))
                    *word = toupper(*word);
            if(*word == '1')
                    *word = 'L';
            if(*word == '0')

                    *word = 'O';
            if(*word == '5')
                    *word = 'S';
            word++;
    }
}

/* Extract 'length' bits from the char array 's' starting with bit 'start' */
static unsigned long
extract(s, start, length)
char *s;
int start, length;
{
    unsigned char cl;
    unsigned char cc;
    unsigned char cr;
    unsigned long x;

    assert(length <= 11);
    assert(start >= 0);
    assert(length >= 0);
    assert(start +length <= 66);

    cl = s[start/8];
    cc = s[start/8 +1];
    cr = s[start/8 +2];
    x = ((long)(cl<<8 | cc) <<8  | cr) ;
    x = x >> (24 - (length + (start %8)));
    x =( x & (0xffff >> (16-length) )   );
    return(x);
}

A.2.5. skey.c

/* Stand-alone program for computing responses to S/Key challenges.
 * Takes the iteration count and seed as command line args, prompts
 * for the user's key, and produces both word and hex format responses.
 *
 * Usage example:
 *      >skey 88 ka9q2
 *      Enter password:
 *      OMEN US HORN OMIT BACK AHOY
 *      C848 666B 6435 0A93
 *      >
 */
  
#ifdef VAXC
#ifndef HASSTDLIB
#define HASSTDLIB
#endif
#endi
 f
#include 
#ifdef HASSTDLIB
#include 
#else
#include 
#endif
#include 
#ifdef  __MSDOS__
#include 
#else   /* Assume BSD unix */
#ifdef VAXC
#include 
#else
#include 
#include 
#endif
#endi
#include "md4.h"
#include "skey.h"

char *readpass();
void usage();
#ifdef VAXC
int     optind = 1;
char    *optarg;
#else
int getopt();
extern int optind;
extern char *optarg;
#endif

#ifdef VAXC
int getopt( argc, argv, optstring)
int     argc;
char    *argv[];
char    *optstring;
{
int     i;

for ( i = optind; i < argc; i++)
{
    if ( strncmp( argv[ i], "-n", 2) == 0)
    {
        optind = i + 2;
        optarg = argv[ i + 1];
        return( argv[ i][ 1]);
    }
}  
return( -1);
}
#endif

int
main(argc,argv)
int argc;
char *argv[];
{
    int n,cnt,i;
    char passwd[256],passwd2[256];
    char key[8];
    char *seed;
    char buf[30];
    char *slash;
   
    cnt = 1;
    while((i = getopt(argc,argv,"n:")) != EOF){
            switch(i){
            case 'n':
                    cnt = atoi(optarg);
                    break;
            }
    }
    /* could be in the form / */
    if(argc <= optind + 1){
            /*look for / in it */
            if(argc <= optind){
                    usage(argv[0]);
                    return 1;
            }
   
            slash = strchr(argv[optind], '/');
            if(slash == NULL){
                    usage(argv[0]);
                    return 1;
            }
            *slash++ = '';
            seed = slash;
   
            if((n = atoi(argv[optind])) < 0){
                    fprintf(stderr,"%s not positiven",argv[optind]);
                    usage(argv[0]);
                    return 1;
            }
    }
    else {
   
            if((n = atoi(argv[optind])) < 0){
                    fprintf(stderr,"%s not positiven",argv[optind]);
                    usage(argv[0]);
                    return 1;
            }
            seed = argv[++optind];
    }
    fprintf(stderr,"Reminder - Do not use key while logged in via telnet or dial-in.n");
   
    /* Get user's secret password */
    for(;;){
            fprintf(stderr,"Enter secret password: ");
            readpass(passwd,sizeof(passwd));
#ifdef VAXC
            fprintf( stderr, "n");
#endif 
            break;
    /************
            fprintf(stderr,"Again secret password: ");
            readpass(passwd2,sizeof(passwd));
            if(strcmp(passwd,passwd2) == 0) break;
            fprintf(stderr, "Sorry no matchn");
    **************/
   
    }
   
    /* Crunch seed and password into starting key */
    if(keycrunch(key,seed,passwd) != 0){
            fprintf(stderr,"%s: key crunch failedn",argv[0]);
            return 1;
    }
    if(cnt == 1){
            while(n-- != 0)
                    f(key);
            printf("%sn",btoe(buf,key));
#ifdef  HEXIN
            printf("%sn",put8(buf,key));
#endif 
    } else {
            for(i=0;i<=n-cnt;i++)
                    f(key);
            for(;i<=n;i++){
#ifdef  HEXIN
                    printf("%d: %-29s
%sn",i,btoe(buf,key),put8(buf,key));
#else  
                    printf("%d: %-29sn",i,btoe(buf,key));
#endif 
                    f(key);
            }

    }
#ifdef VAXC
    return( SS$_NORMAL);
#else  
    return 0;
#endif 
}
void
usage(s)
char *s;
{
    fprintf(stderr,"Usage: %s [-n count] [/]  n",s);
}      

A.2.6. skeyinit.c

/*   change password or add user to S/KEY authentication system.
 *   S/KEY is a tradmark of Bellcore  */

#include 
#include 
#ifdef VAXC
#include 
#include 
#include 
#include 
#include 
#else
#include 
#endif
#include "skey.h"
#include 
#include 

extern int optind;
extern char *optarg;

char * readpass();

int skeylookup __ARGS((struct skey *mp,char *name));

#define NAMELEN 2

#ifdef VAXC
void uppercase( string)     /* uppercase a string in-situ */
char    *string;
{
char    *cp;

cp = string;
while ( *cp != '')
{
    *cp = toupper( *cp);
    cp++;
}  
}
#endif

int
main(argc,argv)
int argc;
char *argv[];
{
    struct skey skey;
    int rval,n,nn,i,defaultsetup;
    char seed[18],tmp[80],key[8];
    char defaultseed[17], passwd[256],passwd2[256] ;
#ifdef VAXC
    char    username[ 255];
    struct passwd
    {
        char    *pw_name;
    } vms_username = { username};
    struct passwd   *pp = &vms_username;
    int                 ret_length;
    struct
    {
        unsigned short      len;
        unsigned short      item;
        char                *buffer;
        int                 *ret_len;
        int                 terminator;
    } item_list = { sizeof( username), JPI$_USERNAME, username,
&ret_length,
 0};
    unsigned int        status;
    struct
    {
        unsigned int        status;
        unsigned short      ret_length;
        unsigned short      padding;
    } iosb;
    struct dsc$descriptor_s name_desc =
        { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, username};
    unsigned long           auth_privs;
    char                    *cp;
#else  
    struct passwd *ppuser,*pp;
#endif 


    time_t now;
    struct tm *tm;
    char tbuf[27],buf[60];
    char lastc, me[80];
    int l;
   
    time(&now);
    tm = localtime(&now);
    strftime(tbuf, sizeof(tbuf), "%M%j", tm);
    gethostname(defaultseed,NAMELEN);
    strcpy(&defaultseed[NAMELEN],tbuf);
   
#ifdef VAXC
    status = sys$getjpi( 0, 0, 0, &item_list, &iosb, 0, 0);
    if ( status != SS$_NORMAL)
    {
        exit( status);
    }
    if ( iosb.status != SS$_NORMAL)
    {
        exit( iosb.status);
    }
    cp = strchr( username, ' ');
    if ( cp != NULL)
    {
        *cp = '';
    }
#else  
    pp = ppuser = getpwuid(getuid());
#endif 
    strcpy(me,pp->pw_name);
    defaultsetup = 1;
    if( argc > 1){
            if(strcmp("-s", argv[1]) == 0)
                    defaultsetup = 0;
            else
#ifdef VAXC
                    strcpy( pp->pw_name, argv[ 1]);
#else  
                    pp = getpwnam(argv[1]);
#endif 
            if(argc > 2)
#ifdef VAXC
                    strcpy( pp->pw_name, argv[ 2]);
#else  
                    pp = getpwnam(argv[2]);
#endif 

    }
#ifdef VAXC
    uppercase( username);
    name_desc.dsc$w_length = strlen( username);
    status = sys$asctoid( &name_desc, 0, 0);
    if ( status != SS$_NORMAL)
    {
        printf( "User unknownn");
        return( 1);
    }
#else  
    if(pp == NULL){
            printf("User unknownn");
            return 1;
    }
#endif 
    if(strcmp( pp->pw_name,me) != 0){
#ifdef VAXC
            item_list.buffer = ( char *) &auth_privs;
            item_list.len = sizeof( auth_privs);
            item_list.item = JPI$_AUTHPRIV;
            status = sys$getjpi( 0, 0, 0, &item_list, &iosb, 0, 0);
            if ( status != SS$_NORMAL)
            {
                printf("Permission denied.n");
                return(1);
            }
            if ( ( auth_privs & ( PRV$M_SETPRV | PRV$M_SYSPRV |
PRV$M_BYPASS
)) == 0)
            {
                printf("Permission denied.n");
                return(1);
            }
#else
            if(getuid() != 0){
                    /* Only root can change other's passwds */
                    printf("Permission denied.n");
                    return(1);
            }
#endif
    }




    rval = skeylookup(&skey,pp->pw_name);
    switch(rval){
    case -1:
            perror("error in opening database");
            return 1;
    case 0:
            printf("Updating %s:n",pp->pw_name);
            printf("Old key: %sn",skey.seed);
            /* lets be nice if they have a skey.seed that ends in 0-8 just
a
dd one*/
            l = strlen(skey.seed);
            if( l > 0){
                    lastc = skey.seed[l-1];
                    if( isdigit(lastc) && lastc != '9' ){
                            strcpy(defaultseed, skey.seed);
                            defaultseed[l-1] = lastc + 1;
                    }
                    if( isdigit(lastc) && lastc == '9' && l < 16){
                            strcpy(defaultseed, skey.seed);
                            defaultseed[l-1] = '0';
                            defaultseed[l] = '0';
                            defaultseed[l+1] = '';
                    }
            }
            break;
    case 1:
            printf("Adding %s:n",pp->pw_name);
            break;
    }
n = 99;
if( ! defaultsetup){
    printf("Reminder you need the 6 english words from the skey
command.n")
;
    for(i=0;;i++){
            if(i >= 2) exit(1);
            printf("Enter sequence count from 1 to 10000: ");
            fgets(tmp,sizeof(tmp),stdin);
            n = atoi(tmp);
            if(n > 0 && n < 10000)
                    break;  /* Valid range */
            printf("Count must be > 0 and < 10000n");
    }
}
if( !defaultsetup){
    printf("Enter new key [default %s]: ", defaultseed);
    fflush(stdout);
    fgets(seed,sizeof(seed),stdin);
    rip(seed);
    if(strlen(seed) > 16){
            printf("Seed truncated to 16 charsn");
            seed[16] = '';
    }
    if( seed[0] == '') strcpy(seed,defaultseed);
    for(i=0;;i++){
            if(i >= 2) exit(1);
            printf("s/key %d %sns/key access password: ",n,seed);
            fgets(tmp,sizeof(tmp),stdin);
            rip(tmp);
            backspace(tmp);
            if(tmp[0] == '?'){
                    printf("Enter 6 English words from secure S/Key
calculat
ion.n");
                    continue;
            }
            if(tmp[0] == ''){
                    exit(1);
            }
#ifdef VAXC
            cp = tmp;
            while ( *cp != '')
            {
                if ( *cp == '.')
                {
                    *cp = ' ';
                }
                cp++;
            }
#endif
            if(etob(key,tmp) == 1 || atob8(key,tmp) == 0)
                    break;  /* Valid format */
            printf("Invalid format, try again with 6 English words.n");
    }
} else {
    /* Get user's secret password */
    fprintf(stderr,"Reminder - Only use this method if you are direct
connec
ted.n");
    fprintf(stderr,"If you are using telnet or dial-in exit with no
password
 and use keyinit -s.n");
    for(i=0;;i++){
            if(i >= 2) exit(1);
            fprintf(stderr,"Enter secret password: ");
            readpass(passwd,sizeof(passwd));
#ifdef VAXC
            fprintf( stderr, "n");
#endif
            if(passwd[0] == ''){
                    exit(1);
            }
            fprintf(stderr,"Again secret password: ");
            readpass(passwd2,sizeof(passwd));
#ifdef VAXC
            fprintf( stderr, "n");
#endif
            if(passwd2[0] == ''){
                    exit(1);
            }
            if(strlen(passwd) < 4 && strlen(passwd2) < 4) {
                    fprintf(stderr, "Sorry your password must be
longernr"
);
                    exit(1);
            }
            if(strcmp(passwd,passwd2) == 0) break;
            fprintf(stderr, "Sorry no matchn");


    }
    strcpy(seed,defaultseed);

    /* Crunch seed and password into starting key */
    if(keycrunch(key,seed,passwd) != 0){
            fprintf(stderr,"%s: key crunch failedn",argv[0]);
            return 1;
    }
    nn = n;
    while(nn-- != 0)
            f(key);
}
    time(&now);
    tm = localtime(&now);
    strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm);
    if (skey.val == NULL)
              skey.val = (char *) malloc(16+1);

    btoa8(skey.val,key);
    fprintf(skey.keyfile,"%s %04d %-16s %s %-21sn",pp->pw_name,n,
            seed,skey.val, tbuf);
    fclose(skey.keyfile);
    printf("nID %s s/key is %d %sn",pp->pw_name,n,seed);
    printf("%sn",btoe(buf,key));
#ifdef HEXIN
    printf("%sn",put8(buf,key));
#endif

#ifdef VAXC
    return( SS$_NORMAL);
#else
    return 0;
#endif
}

A.2.7. skeylogin.c

/*   Login code for S/KEY Authentication.  S/KEY is a trademark
 *   of Bellcore.
 *
 *   Mink is the former name of the S/KEY authentication system.
 *   Many references for mink  may still be found in this program.   */
#ifdef VAXC
#include 
#else
#include 
#ifdef  QUOTA
#include 
#endif
#include 
#include 
#include 
#include 
#endif

#include 
#include 
#ifdef VAXC
#include 
#include 
#else
#include 
#include 
#endif
#include 
#include 
#include "skey.h"

#ifdef VAXC
#define KEYFILE "sys$manager:skeykeys.dat"
struct timeval
{
long    tv_sec;
long    tv_usec;
};
 
#else
#define KEYFILE "/etc/skeykeys"
#endif
 
char *skipspace();
int skeylookup __ARGS((struct skey *mp,char *name));


/* Issue a skey challenge for user 'name'. If successful,
 * fill in the caller's skey structure and return 0. If unsuccessful
 * (e.g., if name is unknown) return -1.
 *
 * The file read/write pointer is left at the start of the
 * record.
 */
int
getskeyprompt(mp,name,prompt)
struct skey *mp;
char *name;
char *prompt;
{
    int rval;

    sevenbit(name);
    rval = skeylookup(mp,name);
    strcpy(prompt,"s/key 55 latour1n");
    switch(rval){
    case -1:        /* File error */
            return -1;
    case 0:         /* Lookup succeeded, return challenge */
            sprintf(prompt,"s/key %d %sn",mp->n - 1,mp->seed);
            return 0;
    case 1:         /* User not found */
            fclose(mp->keyfile);
            return -1;
    }
    return -1;      /* Can't happen */
}      
/* Return  a skey challenge string for user 'name'. If successful,
 * fill in the caller's skey structure and return 0. If unsuccessful
 * (e.g., if name is unknown) return -1.
 *
 * The file read/write pointer is left at the start of the
 * record.
 */
int
skeychallenge(mp,name, ss)
struct skey *mp;
char *name;
char *ss;
{
    int rval;

    rval = skeylookup(mp,name);
    switch(rval){
    case -1:        /* File error */
            return -1;
    case 0:         /* Lookup succeeded, issue challenge */
            sprintf(ss, "s/key %d %s",mp->n - 1,mp->seed);
            return 0;
    case 1:         /* User not found */
            fclose(mp->keyfile);
            return -1;
    }
    return -1;      /* Can't happen */
}      
   
/* Find an entry in the One-time Password database.
 * Return codes:
 * -1: error in opening database
 *  0: entry found, file R/W pointer positioned at beginning of record
 *  1: entry not found, file R/W pointer positioned at EOF
 */
int
skeylookup(mp,name)
struct skey *mp;
char *name;
{
    int found;
    int len;
    long recstart;
    char *cp;
    struct stat statbuf;

    /* See if the KEYFILE exists, and create it if not */
    if(stat(KEYFILE,&statbuf) == -1 && errno == ENOENT){
            mp->keyfile = fopen(KEYFILE,"w+");
    } else {
            /* Otherwise open normally for update */
            mp->keyfile = fopen(KEYFILE,"r+");
    }
    if(mp->keyfile == NULL)
            return -1;

    /* Look up user name in database */
    len = strlen(name);
    if( len > 8 ) len = 8;          /*  Added 8/2/91  -  nmh */
    found = 0;
    while(!feof(mp->keyfile)){
            recstart = ftell(mp->keyfile);
            mp->recstart = recstart;
            if(fgets(mp->buf,sizeof(mp->buf),mp->keyfile) != mp->buf){
                    break;
            }
            rip(mp->buf);
            if(mp->buf[0] == '#')
                    continue;       /* Comment */
            if((mp->logname = strtok(mp->buf," t")) == NULL)
                    continue;
            if((cp = strtok(NULL," t")) == NULL)
                    continue;
            mp->n = atoi(cp);
            if((mp->seed = strtok(NULL," t")) == NULL)
                    continue;
            if((mp->val = strtok(NULL," t")) == NULL)
                    continue;
            if(strlen(mp->logname) == len
             && strncmp(mp->logname,name,len) == 0){
                    found = 1;
                    break;
            }
    }
    if(found){
            fseek(mp->keyfile,recstart,0);
            return 0;
    } else
            return 1;
}
/* Verify response to a s/key challenge.
 *
 * Return codes:
 * -1: Error of some sort; database unchanged
 *  0:  Verify successful, database updated
 *  1:  Verify failed, database unchanged
 *
 * The database file is always closed by this call.
 */
int
skeyverify(mp,response)
struct skey *mp;
char *response;
{
 struct timeval startval;
 struct timeval endval;
long microsec;
    char key[8];
    char fkey[8];
    char filekey[8];
    time_t now;
    struct tm *tm;
    char tbuf[27],buf[60];
    char me[80];
    int rval;
    char *cp;

    time(&now);
    tm = localtime(&now);
    strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm);

    if(response == NULL){
            fclose(mp->keyfile);
            return -1;
    }
    rip(response);

    /* Convert response to binary */
    if(etob(key,response) != 1 && atob8(key,response) != 0){
            /* Neither english words or ascii hex */
            fclose(mp->keyfile);
            return -1;
    }

    /* Compute fkey = f(key) */
    memcpy(fkey,key,sizeof(key));
    f(fkey);
    /* in order to make the window of update as short as possible
       we must do the comparison here and if OK write it back
       other wise the same password can be used twice to get in
       to the system
    */
#ifndef VAXC
    setpriority(PRIO_PROCESS, 0, -4);
#endif
/*   
  gettimeofday(&startval, (char *)0 );
*/
 
    /* reread the file record NOW*/

    fseek(mp->keyfile,mp->recstart,0);
    if(fgets(mp->buf,sizeof(mp->buf),mp->keyfile) != mp->buf){
#ifndef VAXC
            setpriority(PRIO_PROCESS, 0, 0);
#endif
            fclose(mp->keyfile);
            return -1;
    }
    rip(mp->buf);
    mp->logname = strtok(mp->buf," t");
    cp = strtok(NULL," t") ;
    mp->seed = strtok(NULL," t");
    mp->val = strtok(NULL," t");
    /* And convert file value to hex for comparison */
    atob8(filekey,mp->val);

    /* Do actual comparison */
    if(memcmp(filekey,fkey,8) != 0){
            /* Wrong response */
#ifndef VAXC
            setpriority(PRIO_PROCESS, 0, 0);
#endif
            fclose(mp->keyfile);
            return 1;
    }

    /* Update key in database by overwriting entire record. Note
     * that we must write exactly the same number of bytes as in
     * the original record (note fixed width field for N)
     */
    btoa8(mp->val,key);
    mp->n--;
    fseek(mp->keyfile,mp->recstart,0);
    fprintf(mp->keyfile,"%s %04d %-16s %s
%-21sn",mp->logname,mp->n,mp->see
d,
     mp->val, tbuf);
/*
gettimeofday(&endval, (char *)0 );
 microsec = (endval.tv_sec - startval.tv_sec) * 1000000 + (endval.tv_usec -
star
tval.tv_usec);
fprintf(stderr, "window= %d micro seconds n"  , microsec);
*/


    fclose(mp->keyfile);

#ifndef VAXC
    setpriority(PRIO_PROCESS, 0, 0);
#endif
    return 0;
}


/* Convert 8-byte hex-ascii string to binary array
 * Returns 0 on success, -1 on error
 */
atob8(out,in)
register char *out,*in;
{
    register int i;
    register int val;

    if(in == NULL || out == NULL)
            return -1;

    for(i=0;i<8;i++){
            if((in = skipspace(in)) == NULL)
                    return -1;
            if((val = htoi(*in++)) == -1)
                    return -1;
            *out = val << 4;

            if((in = skipspace(in)) == NULL)
                    return -1;
            if((val = htoi(*in++)) == -1)
                    return -1;
            *out++ |= val;
    }
    return 0;
}

char *
skipspace(cp)
register char *cp;
{
    while(*cp == ' ' || *cp == 't')
            cp++;

    if(*cp == '')
            return NULL;
    else
            return cp;
}

/* Convert 8-byte binary array to hex-ascii string */
int
btoa8(out,in)
register char *out,*in;
{
    register int i;

    if(in == NULL || out == NULL)
            return -1;

    for(i=0;i<8;i++){
            sprintf(out,"%02x",*in++ & 0xff);
            out += 2;
    }
    return 0;
}


/* Convert hex digit to binary integer */
int
htoi(c)
register char c;
{
    if('0' <= c && c <= '9')
            return c - '0';
    if('a' <= c && c <= 'f')
            return 10 + c - 'a';
    if('A' <= c && c <= 'F')
            return 10 + c - 'A';
    return -1;
}

A.2.8. skeysubr.c

#include 
#ifdef VAXC
#ifndef HASSTDLIB
#define HASSTDLIB
#endif
#include 
#include 
#include 
#endif
 
#ifdef HASSTDLIB
#include 
#else
#include 
#endif
#include 
#ifdef  __MSDOS__
#include 
#else   /* Assume BSD unix */
#ifndef VAXC
#include 
#include 
#endif
#endi
#include "md4.h"
#include "skey.h"

#ifdef VAXC         /* VAXC hates splitting the #if line */
#define LITTLE_ENDIAN
#else
#if (defined(__MSDOS__) || defined(MPU8086) || defined(MPU8080) 
 || defined(vax) || defined (MIPSEL))
#define LITTLE_ENDIAN   /* Low order bytes are first in memory */
#endif                  /* Almost all other machines are big-endian */
#endif
 
/* Crunch a key:
 * concatenate the seed and the password, run through MD4 and
 * collapse to 64 bits. This is defined as the user's starting key.
 */
int
keycrunch(result,seed,passwd)
char *result;   /* 8-byte result */
char *seed;     /* Seed, any length */
char *passwd;   /* Password, any length */
{
    char *buf;
    MDstruct md;
    unsigned int buflen;
#ifndef LITTLE_ENDIAN
    int i;
    register long tmp;
#endif
   
    buflen = strlen(seed) + strlen(passwd);
    if((buf = malloc(buflen+1)) == NULL)
            return -1;
    strcpy(buf,seed);
    strcat(buf,passwd);

    /* Crunch the key through MD4 */
    sevenbit(buf);
    MDbegin(&md);
    MDupdate(&md,(unsigned char *)buf,8*buflen);

    free(buf);

    /* Fold result from 128 to 64 bits */
    md.buffer[0] ^= md.buffer[2];
    md.buffer[1] ^= md.buffer[3];

#ifdef  LITTLE_ENDIAN
    /* Only works on byte-addressed little-endian machines!! */
    memcpy(result,(char *)md.buffer,8);
#else
    /* Default (but slow) code that will convert to
     * little-endian byte ordering on any machine
     */
    for(i=0;i<2;i++){
            tmp = md.buffer[i];
            *result++ = tmp;
            tmp >>= 8;
            *result++ = tmp;
            tmp >>= 8;
            *result++ = tmp;
            tmp >>= 8;
            *result++ = tmp;
    }
#endif
 
    return 0;
}

/* The one-way function f(). Takes 8 bytes and returns 8 bytes in place */
void
f(x)
char *x;
{      
    MDstruct md;
#ifndef LITTLE_ENDIAN
    register long tmp;
#endif
 
    MDbegin(&md);
    MDupdate(&md,(unsigned char *)x,64);

    /* Fold 128 to 64 bits */
    md.buffer[0] ^= md.buffer[2];
    md.buffer[1] ^= md.buffer[3];

#ifdef  LITTLE_ENDIAN
    /* Only works on byte-addressed little-endian machines!! */
    memcpy(x,(char *)md.buffer,8);

#else
    /* Default (but slow) code that will convert to
     * little-endian byte ordering on any machine
     */
    tmp = md.buffer[0];
    *x++ = tmp;
    tmp >>= 8;
    *x++ = tmp;
    tmp >>= 8;
    *x++ = tmp;
    tmp >>= 8;
    *x++ = tmp;

    tmp = md.buffer[1];
    *x++ = tmp;
    tmp >>= 8;
    *x++ = tmp;
    tmp >>= 8;
    *x++ = tmp;
    tmp >>= 8;
    *x = tmp;
#endif
}    

/* Strip trailing cr/lf from a line of text */
void
rip(buf)
char *buf;
{  
    char *cp;

    if((cp = strchr(buf,'r')) != NULL)
            *cp = '';

    if((cp = strchr(buf,'n')) != NULL)
            *cp = '';
}
/************************/
#ifdef  __MSDOS__
char *
readpass(buf,n)
char *buf;
int n;
{    
    int i;
    char *cp;

    for(cp=buf,i = 0; i < n ; i++)
            if ((*cp++ = bdos(7,0,0)) == 'r')
                    break;
    *cp = '';
    printf("n");
    rip(buf);
    return buf;
}
#else
#ifdef VAXC

char *readpass( buf, n)
char    *buf;
int     n;
{
    $DESCRIPTOR( terminal, "TT");
    unsigned int    status;
    struct
    {
        unsigned int    status;
        unsigned short  ret_len;
        unsigned short  filler;
    } iosb;
    unsigned short      channel;

    status = sys$assign( &terminal, &channel, 0, 0);
    if ( status != SS$_NORMAL)
    {
        return( NULL);
    }

    status = sys$qiow( 0, channel, IO$_READVBLK | IO$M_NOECHO, &iosb, 0, 0,
                        buf, n, 0, 0, 0, 0);
    if ( ( status != SS$_NORMAL) || ( iosb.status != SS$_NORMAL))
    {
        return( NULL);
    }

    fprintf(stderr, "n");
    fflush(stderr);
    sevenbit(buf);

    return( buf);
}

#else
char *
readpass(buf,n)
char *buf;
int n;
{    
    int fflags,lword,lwordsav;
    struct sgttyb ttyf,ttysave;

    /* Set normal line editing */
    fflags = fcntl(fileno(stdin),F_GETFL,0);
    fcntl(fileno(stdin),F_SETFL,fflags & ~FNDELAY);
    ioctl(fileno(stdin),TIOCLGET,&lword);
    ioctl(fileno(stdin),TIOCLGET,&lwordsav);
    lword |= LCRTERA|LCRTKIL;
    ioctl(fileno(stdin),TIOCLSET,&lword);

    /* Turn off echoing */
    ioctl(fileno(stdin), TIOCGETP, &ttyf);
    ioctl(fileno(stdin), TIOCGETP, &ttysave);
    ttyf.sg_flags &= ~(ECHO|RAW|CBREAK);
    ttyf.sg_flags |= CRMOD;
    ioctl(fileno(stdin),TIOCSETP,&ttyf);
    fgets(buf,n,stdin);
    rip(buf);

    /* Restore previous tty modes */
    fcntl(fileno(stdin),F_SETFL,fflags);
    ioctl(fileno(stdin),TIOCSETP,&ttysave);
    ioctl(fileno(stdin),TIOCLSET,&lwordsav);

    /*
    after the secret key is taken from the keyboard, the line feed is
    written to standard error instead of standard output.  That means that
    anyone using the program from a terminal won't notice, but capturing
    standard output will get the key words without a newline in front of
    them.
    */
    fprintf(stderr, "n");
    fflush(stderr);
    sevenbit(buf);

    return buf;
}

#endif
#endi
 f
/* removebackspaced over charaters from the string*/
backspace(buf)
char *buf;
{
    char bs = 0x8;
    char *cp = buf;
    char *out = buf;

    while(*cp){
            if( *cp == bs ) {
                    if(out == buf){
                            cp++;
                            continue;
                    }
                    else {
                      cp++;
                      out--;
                    }
            }
            else {
                    *out++ = *cp++;
            }

    }
    *out = '';

}      
sevenbit(s)
char *s;
{      
    /* make sure there are only 7 bit code in the line*/
    while(*s){
            *s = 0x7f & ( *s);
            s++;
    }
}

A.2.9. skey_challenge.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "challenge.h"
#include "skey.h"

/*
**  External definitions
*/
extern unsigned int     sys$qiow();
extern unsigned int     sys$delprc();
extern long int         sys$gettim();
extern unsigned int     sys$getdviw();
extern unsigned int     sys$dassgn();
extern void             lib$date_time();

/*
**  Function prototypes
*/
void    kill_process();

/*
**  Global variables
*/
FILE        *verbose_log_file;
FILE        *log_file;
BOOLEAN     exit_ok = FALSE;
char        dvi_buffer[ DVI_BUF_LEN];
int         dvi_buffer_len;


char *get_time( timestamp)
char    *timestamp;
{
time_t                      time_str;
char                        *cp;
struct dsc$descriptor_s     time_desc;

time_str = time( NULL);
/*
**  asctime() puts a 'n' on the end that we don't want - smash it to ''
*/
strcpy( timestamp, asctime( localtime( &time_str)));
cp = timestamp;
while ( ( *cp != '') && ( *cp != 'n'))
{
    cp++;
}
if ( *cp == 'n')
{
    *cp = '';
}

time_desc.dsc$a_pointer = timestamp;
time_desc.dsc$w_length = TIME_BUF_LEN;
lib$date_time( &time_desc);
cp = timestamp;
while ( ( *cp != '') && ( *cp != '.'))
{
    cp++;
}
*cp = '';
return( timestamp);
} /* end get_time */


/*
**  log_message
**
**  Logger if running verbose mode (implies debugging code).
**
*/


void log_message (message)
char * message;
{

#ifdef VERBOSE

    char timestamp[TIME_BUF_LEN];

    verbose_log_file = fopen( VERBOSE_LOG, "a");
    if ( verbose_log_file != NULL)
    {
        fprintf( verbose_log_file, "%st%sn", get_time(timestamp),
message)
;
        fclose( verbose_log_file);
    }

#endif /* VERBOSE */

} /* end log_message */


/*
**  exit_handler
**
**  Exit handler to catch inadvertent exiting.  If exit_ok
**  has been set, we will exit.  Else, we will kill the
**  process.
**
*/
void exit_handler()
{
log_message( "exit_handler: Beginning exit handler...");
if ( exit_ok)
{
    log_message( "exit_handler: ...returned");
    return;
}
else
{  
    log_message( "exit_handler: ...failed");
    log_file = fopen( LOG_FILE, "a");
    if ( log_file != NULL)
    {
        fprintf( log_file, "tProcess caught in the exit handlern");
        fclose( log_file);
    }
    kill_process();
}
}   


/*
**  kill_process
**
**  Kills the current process (on Unix, the parent process)
**
*/
 
void kill_process()
{
unsigned int    status;

#ifndef NO_KILL
status = sys$delprc( 0, 0);
#endif
 
log_message( "kill_process: Killed!");
printf( "Killed!n");
exit_ok = TRUE;
exit( SS$_NORMAL);
}


int get_response( buffer, buf_len)
char    *buffer;
int     buf_len;
{
unsigned int        status;
int                 response = 0;
static          $DESCRIPTOR( input_dev_desc, "TT");
unsigned int    term_chan;
struct
{
    short int   buf_len;
    short int   item_code;
    char        *buffer;
    int         *ret_len;
    int         terminator;
} dvi_item_list = { DVI_BUF_LEN, DVI$_TT_ACCPORNAM, dvi_buffer,
&dvi_buffer_
len, 0};

log_message( "Beginning get_response");

log_message( "get_response: Issuing sys$assign...");
status = sys$assign( &input_dev_desc,
                        &term_chan,
                        0,
                        0);
log_message( "...Done");
if ( status != SS$_NORMAL)
{
    kill_process();
}

log_message( "get_response: Issuing sys$getdviw...");
status = sys$getdviw( 0,
                        term_chan,
                        0,
                        &dvi_item_list,
                        0,
                        0,
                        0,
                        0);
log_message( "...Done");
if ( status != SS$_NORMAL)
{
    kill_process();
}

log_message( "get_response: Issuing sys$qiow...");
status = sys$qiow( 0,
                    term_chan,
                    IO$_READVBLK | IO$M_TIMED,
                    0,
                    0,
                    0,
                    buffer,
                    buf_len,
                    30,
                    0, 0, 0);
log_message( "...Done");

if ( status != SS$_NORMAL)
{
    log_message( "get_response: Problem waiting for response");
    if ( status == SS$_TIMEOUT)
    {
        log_message( "get_response: Timeout waiting for response");
        log_file = fopen( LOG_FILE, "a");
        if ( log_file != NULL)
        {
            fprintf( log_file, "tTimeout waiting for responsen");
            fclose( log_file);
        }
    }
    kill_process();
}

log_message( "get_response: Got response okay");
log_message( "get_response: Issuing sys$dassgn...");
status = sys$dassgn( term_chan);
log_message( "...Done");

log_message( "get_response: Now leaving get_response successfully");
return;
}


int main()
{
int             challenge;
int             response = 0;
char            buffer[ BUFFER_LEN];
unsigned int    status;
char            time_buf[ TIME_BUF_LEN];
char            *connection_source = dvi_buffer;
unsigned int    exit_status = SS$_NORMAL;
struct
{
    int             flink;
    unsigned int    addr;
    int             arg_count;
    int             reason_addr;
} desblk = { 0, &exit_handler, 0, &exit_status};
union
{
    long int        time;
    struct
    {
        int     top;
        int     bottom;
    } t;
} time_blk;
char            jpi_buffer[ JPI_BUF_LEN];
int             jpi_buffer_len;
char            jpi_buffer1[ JPI_BUF_LEN];
int             jpi_buffer_len1;
struct
{
    short int   buf_len;
    short int   function_code;
    char        *jpi_buffer;
    int         *ret_buffer_len;
    short int   buf_len1;
    short int   function_code1;
    char        *jpi_buffer1;
    int         *ret_buffer_len1;
    int         terminator;
} jpi_item_list = { JPI_BUF_LEN, JPI$_MODE, jpi_buffer, &jpi_buffer_len,
                    JPI_BUF_LEN, JPI$_USERNAME, jpi_buffer1,
&jpi_buffer_len
1, 0};
$DESCRIPTOR( time_desc, time_buf);

char            dev_buffer[ DVI_BUF_LEN];
int             dev_buffer_len;
static          $DESCRIPTOR( input_dev_desc, "TT");
unsigned int    term_chan;
struct
{
    short int   buf_len;
    short int   item_code;
    char        *buffer;
    int         *ret_len;
    int         terminator;
} dvi_item_list = { DVI_BUF_LEN, DVI$_TT_PHYDEVNAM, dev_buffer, &dev_buffer_len, 0};
struct skey     skey;
char            skeyprompt[ 80];
int             found;
char            pbuf[ ANSWER_LEN];
char            *cp;

log_message( "main: Issuing sys$dclexh...");
status = sys$dclexh( &desblk);
log_message( "...Done");
if ( status != SS$_NORMAL)
{
    kill_process();
}

log_message( "main: Issuing sys$gettim...");
status = sys$gettim( &time_blk.time);
log_message( "...Done");
if ( status != SS$_NORMAL)
{
    kill_process();
}
time_desc.dsc$w_length = TIME_BUF_LEN;
log_message( "main: Issuing sys$asctim...");
status = sys$asctim( 0, &time_desc, 0, 0);
log_message( "...Done");
if ( status != SS$_NORMAL)
{
    kill_process();
}

log_message( "main: Issuing sys$assign...");
status = sys$assign( &input_dev_desc,
                        &term_chan,
                        0,
                        0);
log_message( "...Done");
if ( status != SS$_NORMAL)
{
    kill_process();
}

log_message( "main: Issuing sys$getdviw...");
status = sys$getdviw( 0,
                        term_chan,
                        0,
                        &dvi_item_list,
                        0,
                        0,
                        0,
                        0);
log_message( "...Done");
if ( status != SS$_NORMAL)
{
    kill_process();
}

log_message( "main: Issuing sys$dassgn...");
status = sys$dassgn( term_chan);
log_message( "...Done");
log_message( dev_buffer);

if ( ( strlen( dev_buffer) == 0))
/* || ( strncmp( dev_buffer, "_TWA", 4) == 0)) */
{
    exit_ok = TRUE;
    exit( SS$_NORMAL);
}

log_message( "main: Issuing sys$getjpi...");
status = sys$getjpi( 0,
                        0,
                        0,
                        &jpi_item_list,
                        0,
                        0,
                        0);
log_message( "...Done");
if ( status != SS$_NORMAL)
{
    kill_process();
}
if ( ( int) jpi_buffer[ 0] != JPI$K_INTERACTIVE)
{
    log_file = fopen( LOG_FILE, "r");
    if ( log_file != NULL)
    {
        if ( fgets( buffer, BUFFER_LEN, log_file) != NULL)
        {
            kill_process();
        }
    }
    exit_ok = TRUE;
    exit( SS$_NORMAL);
}
cp = strchr( jpi_buffer1, ' ');
if ( cp != NULL)
{
    *cp = '';
}
found = skeychallenge( &skey, jpi_buffer1, skeyprompt);
if ( found == 1)
{
    exit_ok = TRUE;
    exit( SS$_NORMAL);
}
if ( found == -1)
{
    kill_process();
}
printf( "%s: ", skeyprompt);
get_response( pbuf, ANSWER_LEN);

if ( skeyverify( &skey, pbuf) != 0)
{
    log_file = fopen( LOG_FILE, "a");
    if ( log_file != NULL)
    {
        fprintf( log_file, "%s: Connect from '%s' failed S/Keyn", time_buf, connection_source);
        fclose( log_file);
    }
    kill_process();
}
else
{  
    exit_ok = TRUE;
    printf( "n");
    log_file = fopen( LOG_FILE, "r");
    if ( log_file != NULL)
    {
        while ( fgets( buffer, BUFFER_LEN, log_file) != NULL)
        {
            printf( "%s", buffer);
        }
        fclose( log_file);
    }

    exit( SS$_NORMAL);
}
}   

A.2.10. lgi$callout_skey.c

/*
**  LOGINOUT Callout LGI$ICR_AUTHENTICATE which implements S/Key
**  one time password checks.
**
**  If the VMS account as more than one password, then we cannot use
**  one time passwords (who holds which list?).  If the user is
**  initialised to use S/Key, but fails their password test, we
**  attempt to see if the password supplied is their VMS password.
**
**  Author:  Danny Smith
**  Date:  7-Nov-1993.
**
**  This program can be linked using the following example commands:
**
**      $ CC LGI$CALLOUT_SKEY.C
**      $ LINK/SHARE=LGI$CALLOUT_SKEY SYS$INPUT/OPT
**      LGI$CALLOUT_SKEY.OBJ, SYS$SHARE:VAXCRTL.EXE/SHARE
**      UNIVERSAL=LGI$LOGINOUT_CALLOUTS
**
**  Then the following steps are required to install it:
**
**      $ DEFINE/SYSTEM/EXEC LGI$LOGINOUT_CALLOUTS LGI$CALLOUT_SKEY
**
**  If the file is not located is SYS$SHARE, also define:
**
**      $ DEFINE/SYSTEM/EXEC LGI$CALLOUT_SKEY 
**
**  Then the image must be installed
**
**      $ INSTALL REPLACE SYS$SHARE:LGI$CALLOUT_SKEY.EXE
**
**  Finally, the SYSGEN parameter LGI_CALLOUTS needs to be set to the number
**  of callout modules (at this stage, 1 only)
**
**      $ RUN SYS$SYSTEM:SYSGEN
**      SYSGEN> SET LGI_CALLOUTS 1
**      SYSGEN> WRITE CURRENT
**      SYSGEN> WRITE ACTIVE
**      SYSGEN> EXIT
**
**  Edit History:
**
**  0   DFS     Initial Version (7-Nov-1993).
**
**  [End of Revision History]
*/
 
/*
**  Header files
*/
#include 
#include 
#include 
#include 
#include 
#include 
#include "skey.h"

#define SKEYPROMPT_LEN  80
#define PASSWORD_LEN    32
#define USERNAME_LEN    32

/*
**  Structures for callout vector and callout arguments vector
*/
struct LGI$CALLOUT_VECTOR
{
long int                    LGI$L_ICR_ENTRY_COUNT;
int                         ( *LGI$ICR_INIT) ();
int                         ( *LGI$ICR_IACT_START) ();
int                         ( *LGI$ICR_DECWINIT) ();
int                         ( *LGI$ICR_IDENTIFY) ();
int                         ( *LGI$ICR_AUTHENTICATE) ();
int                         ( *LGI$ICR_CHKRESTRICT) ();
int                         ( *LGI$ICR_FINISH) ();
int                         ( *LGI$ICR_LOGOUT) ();
int                         ( *LGI$ICR_JOBSTEP) ();
};
 
struct LGI$ARG_VECTOR
{
int                         ( *LGI$ICB_GET_INPUT) ();
int                         ( *LGI$ICB_DECW_IDENT) ();
int                         ( *LGI$ICB_DECW_AUTH) ();
void                        ( *LGI$ICB_GET_SYSPWD) ();
int                         ( *LGI$ICB_USERPROMPT) ();
int                         ( *LGI$ICB_USERPARSE) ();
int                         ( *LGI$ICB_AUTOLOGIN) ();
int                         ( *LGI$ICB_PASSWORD) ();
int                         ( *LGI$ICB_CHECK_PASS) ();
int                         ( *LGI$ICB_VALIDATE) ();
void                        ( *LGI$ICB_ACCTEXPIRED) ();
void                        ( *LGI$ICB_PWDEXPIRED) ();
int                         ( *LGI$ICB_DISUSER) ();
void                        ( *LGI$ICB_MODALHOURS) ();
short int                   *LGI$A_ICR_CREPRC_FLAGS;
char                        *LGI$A_ICR_JOB_TYPE;
char                        *LGI$A_ICR_SUBPROCESS;
char                        *LGI$A_ICR_TERMINAL_DEV;
struct dsc$descriptor_s     *LGI$A_ICR_TT_PHYDEVNAM;
struct dsc$descriptor_s     *LGI$A_ICR_TT_ACCPORNAM;
struct dsc$descriptor_s     *LGI$A_ICR_CLINAME;
struct dsc$descriptor_s     *LGI$A_ICR_CLITABLES;
struct dsc$descriptor_s     *LGI$A_ICR_NCB;
int                         *LGI$A_ICR_LOGLINK;
struct dsc$descriptor_s     *LGI$A_ICR_REM_NODE_NAME;
struct dsc$descriptor_s     *LGI$A_ICR_REM_ID;
unsigned int                *LGI$A_ICR_UAF_RECORD;
struct RAB                  *LGI$A_ICR_INPUT_RAB;
char                        *LGI$A_ICR_AUTOLOGIN;
struct dsc$descriptor_s     *LGI$A_ICR_USERNAME;
struct dsc$descriptor_s     *LGI$A_ICR_PWD1;
struct dsc$descriptor_s     *LGI$A_ICR_PWD2;
int                         *LGI$A_ICR_PWDCOUNT;
short int                   *LGI$A_ICR_NETFLAGS;
};
 
globalvalue int                 LGI$_SKIPRELATED;
globalvalue int                 LGI$_DISUSER;
globalvalue int                 LGI$_INVPWD;
globalvalue int                 LGI$_NOSUCHUSER;
globalvalue int                 LGI$_NOTVALID;
globalvalue int                 LGI$_INVINPUT;
globalvalue int                 LGI$_CMDINPUT;
globalvalue int                 LGI$_FILEACC;

static int                      callout_decwinit();
static int                      callout_authenticate();

globaldef struct LGI$CALLOUT_VECTOR             LGI$LOGINOUT_CALLOUTS =
{
9,                          /* entry count */
0,                          /* init */
0,                          /* iact_start */
callout_decwinit,           /* decwinit */
0,                          /* identify */
callout_authenticate,       /* authenticate */
0,                          /* chkrestrict */
0,                          /* finish */
0,                          /* logout */
0                           /* jobstep */
};
 
/*
**  DECwindows initialisation
*/
static int callout_decwinit( arg_vector, context)
struct LGI$ARG_VECTOR   *arg_vector;
int                     *context;
{
/* disable any further callouts */
LGI$LOGINOUT_CALLOUTS.LGI$L_ICR_ENTRY_COUNT = 0;

/* return and do normal DECwindows processing */
return( SS$_NORMAL);
}

/*
**  Authenticate
**
**  If the account has two passwords, we cannot be using S/Key as this
**  system currently only supports a single one time password for the
**  account.  Therefore, do normal password processing.
**
**  If the user is found in S/Key, prompt for the S/Key password.  If it
**  is not valid, check to see if the password is the standard VMS
**  password.  Validate if it is.  Any errors in the S/Key system will
**  cause normal VMS authentication to take place.
*/
static int callout_authenticate( arg_vector, context)
struct LGI$ARG_VECTOR   *arg_vector;
int                     *context;
{
int                         status;
char                        skeyprompt[ SKEYPROMPT_LEN + 1];
struct dsc$descriptor_s     skeyprompt_desc =
    { SKEYPROMPT_LEN, DSC$K_DTYPE_T, DSC$K_CLASS_S, skeyprompt};
char                        password[ PASSWORD_LEN + 1];
char                        skeypassword[ PASSWORD_LEN + 1];
struct dsc$descriptor_s     password_desc =
    { PASSWORD_LEN, DSC$K_DTYPE_T, DSC$K_CLASS_S, password};
struct skey                 skey;
int                         found;
char                        username[ USERNAME_LEN + 1];
char                        *cp;

/*
**  This system can only deal with interactive jobs
*/
if ( !( *arg_vector->LGI$A_ICR_CREPRC_FLAGS & PRC$M_INTER))
{
    return( SS$_NORMAL);    /* not interactive - do normal processing
*/
}

if ( *arg_vector->LGI$A_ICR_CREPRC_FLAGS & PRC$M_NOPASSWORD)
{
    return( SS$_NORMAL);    /* invoked as logged in - don't prompt */
}

if ( *arg_vector->LGI$A_ICR_SUBPROCESS != 0)
{
    return( SS$_NORMAL);    /* don't prompt on subprocesses */
}

/*
**  If the account does not have a password, then don't prompt
*/
if ( *arg_vector->LGI$A_ICR_PWDCOUNT == 0)
{
    return( LGI$_SKIPRELATED);      /* continue processing */
}

/*
**  Can only check accounts with a single password
*/
if ( *arg_vector->LGI$A_ICR_PWDCOUNT == 1)
{
    strncpy( username, arg_vector->LGI$A_ICR_USERNAME->dsc$a_pointer, USERNAME_LEN);

/*
**  Ensure there are no trailing spaces
*/
    cp = strchr( username, ' ');
    if ( cp != NULL)
    {
        *cp = '';
    }

/*
**  Is this user in the S/Key system?
**  prompt *ABSOLUTELY MUST* start with 
*/
    strcpy( skeyprompt, "rn");
    cp = skeyprompt;
    cp += strlen( skeyprompt);
    found = skeychallenge( &skey, username, cp);
    strcat( skeyprompt, ": ");
    if ( found == 0)                /* found a user */
    {
        skeyprompt_desc.dsc$w_length = strlen( skeyprompt);

/*
**  Get the password, but don't validate it - we'll do that!
*/
        status = arg_vector->LGI$ICB_PASSWORD( -3, &skeyprompt_desc, &password_desc);
        if ( $VMS_STATUS_SUCCESS( status))
        {
/*
**  Now check the password with S/Key
*/
            strncpy( skeypassword, password, password_desc.dsc$w_length);
            cp = skeypassword;
            cp += password_desc.dsc$w_length;
            *cp = '';
            cp = skeypassword;
            while ( *cp != '')
            {
                if ( *cp == '.')
                {
                    *cp = ' ';
                }
                cp++;
            }
            status = skeyverify( &skey, skeypassword);
            if ( status == 0)
            {
/*
**  Victory!!  Let them in!
*/
                return( LGI$_SKIPRELATED);
            }
            else
            {
/*
**  It may be their VMS password!!
*/
                if ( arg_vector->LGI$ICB_VALIDATE != NULL)
                {
                    status = arg_vector->LGI$ICB_VALIDATE(
                            arg_vector->LGI$A_ICR_USERNAME, &password_desc, 0);
                    if ( $VMS_STATUS_SUCCESS( status))
                    {
                        return( LGI$_SKIPRELATED);
                    }
                    else
                    {
                        return( status);
                    }
                }
                else
                {
                    return( -4);
                }
            }
        }
        else
        {
/*
**  couldn't get any password - just return the status
*/
            return( status);
        }
    }
}

/*
**  Account has at least one password, and is not in S/Key system.
**  Do normal VMS processing
*/
strcpy( skeyprompt, "rnPassword: ");
skeyprompt_desc.dsc$w_length = strlen( skeyprompt);
status = arg_vector->LGI$ICB_PASSWORD( 0, &skeyprompt_desc, 0);
if ( !$VMS_STATUS_SUCCESS( status))
{
    return( status);        /* on error, return status here */
}

/*
**  Does this account have a secondary password?
*/
if ( *arg_vector->LGI$A_ICR_PWDCOUNT == 2)
{
    status = arg_vector->LGI$ICB_PASSWORD( 1, &skeyprompt_desc, 0);
    if ( !$VMS_STATUS_SUCCESS( status))
    {
        return( status);    /* on error, return status here */
    }
}

/*
**  Successful prompt and password validation, skip VMS policy
*/
return( LGI$_SKIPRELATED);
}

A.2.11. strftime.c

#include 
#include 

/*
 * strftime --- produce formatted time
 */
  
int strftime(s, maxsize, format, timeptr)
char *s;
int maxsize;
char *format;
struct tm *timeptr;
{
    char *endp = s + maxsize;
    char *start = s;
    char tbuf[100];
    int i;

    /*
     * various tables, useful in North America
     */
    static char *days_a[] = {
            "Sun", "Mon", "Tue", "Wed",
            "Thu", "Fri", "Sat",
    };
    static char *days_l[] = {
            "Sunday", "Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday", "Saturday",
    };
    static char *months_a[] = {
            "Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    };
    static char *months_l[] = {
            "January", "February", "March", "April",
            "May", "June", "July", "August", "September",
            "October", "November", "December",
    };
    static char *ampm[] = { "AM", "PM", };

    if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0)
            return 0;

    if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
            return 0;

    for (; *format && s < endp - 1; format++) {
            tbuf[0] = '';
            if (*format != '%') {
                    *s++ = *format;
                    continue;
            }
            switch (*++format) {
            case '':
                    *s++ = '%';
                    goto out;

            case '%':
                    *s++ = '%';
                    continue;

            case 'a':       /* abbreviated weekday name */
                    strcpy(tbuf, days_a[timeptr->tm_wday]);
                    break;

            case 'A':       /* full weekday name */
                    strcpy(tbuf, days_l[timeptr->tm_wday]);
                    break;

            case 'b':       /* abbreviated month name */
                    strcpy(tbuf, months_a[timeptr->tm_mon]);
                    break;

            case 'B':       /* full month name */
                    strcpy(tbuf, months_l[timeptr->tm_mon]);
                    break;

            case 'c':       /* appropriate date and time representation */
                    sprintf(tbuf, "%s %s %2d %02d:%02d:%02d %d",
                            days_a[timeptr->tm_wday],
                            months_a[timeptr->tm_mon],
                            timeptr->tm_mday,
                            timeptr->tm_hour,
                            timeptr->tm_min,
                            timeptr->tm_sec,
                            timeptr->tm_year + 1900);
                    break;

            case 'd':       /* day of the month, 01 - 31 */
                    sprintf(tbuf, "%02d", timeptr->tm_mday);
                    break;

            case 'H':       /* hour, 24-hour clock, 00 - 23 */
                    sprintf(tbuf, "%02d", timeptr->tm_hour);
                    break;

            case 'I':       /* hour, 12-hour clock, 01 - 12 */
                    i = timeptr->tm_hour;
                    if (i == 0)
                            i = 12;
                    else if (i > 12)
                            i -= 12;
                    sprintf(tbuf, "%02d", i);
                    break;

            case 'j':       /* day of the year, 001 - 366 */
                    sprintf(tbuf, "%03d", timeptr->tm_yday + 1);
                    break;

            case 'm':       /* month, 01 - 12 */
                    sprintf(tbuf, "%02d", timeptr->tm_mon + 1);
                    break;

            case 'M':       /* minute, 00 - 59 */
                    sprintf(tbuf, "%02d", timeptr->tm_min);
                    break;

            case 'p':       /* am or pm based on 12-hour clock */
                    if (timeptr->tm_hour < 12)
                            strcpy(tbuf, ampm[0]);
                    else
                            strcpy(tbuf, ampm[1]);
                    break;

            case 'S':       /* second, 00 - 61 */
                    sprintf(tbuf, "%02d", timeptr->tm_sec);
                    break;

            case 'T':       /* time as %H:%M:%S */
                    strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr);
                    break;

            case 'w':       /* weekday, Sunday == 0, 0 - 6 */
                    sprintf(tbuf, "%d", timeptr->tm_wday);
                    break;

            case 'x':       /* appropriate date representation */
                    sprintf(tbuf, "%s %s %2d %d",
                            days_a[timeptr->tm_wday],
                            months_a[timeptr->tm_mon],
                            timeptr->tm_mday,
                            timeptr->tm_year + 1900);
                    break;

            case 'X':       /* appropriate time representation */
                    sprintf(tbuf, "%02d:%02d:%02d",
                            timeptr->tm_hour,
                            timeptr->tm_min,
                            timeptr->tm_sec);
                    break;

            case 'y':       /* year without a century, 00 - 99 */
                    i = timeptr->tm_year % 100;
                    sprintf(tbuf, "%d", i);
                    break;

            case 'Y':       /* year with century */
                    sprintf(tbuf, "%d", 1900 + timeptr->tm_year);
                    break;

            default:
                    tbuf[0] = '%';
                    tbuf[1] = *format;
                    tbuf[2] = '';
                    break;
            }
            i = strlen(tbuf);
            if (i)
                    if (s + i < endp - 1) {
                            strcpy(s, tbuf);
                            s += i;
                    } else
                            return 0;
    }
out:
    if (s < endp && *format == '') {
            *s = '';
            return (s - start);
    } else
            return 0;

}

A.3. Utility Command Files

A.3.1. compile.com

$ if P1 .eqs. "CALLOUT_SKEY" then goto callout_skey
$ if P1 .eqs. "SKEYINIT" then goto skeyinit
$ if P1 .eqs. "SKEYSUBR" then goto skeysubr
$ if P1 .eqs. "SKEYLOGIN" then goto skeylogin
$ if P1 .eqs. "MD4" then goto md4
$ if P1 .eqs. "PUT" then goto put
$ if P1 .eqs. "GETHOSTNAME" then goto gethostname
$ if P1 .eqs. "GETSKEYPROMPT" then goto getskeyprompt
$ if P1 .eqs. "SKEY" then goto skey
$ if P1 .eqs. "TEST_SKEY" then goto test_skey
$!
$ if P1 .eqs. "ALL" then goto all
$ write sys$output "Unknown module"
$ exit
$!   
$ callout_skey:
$ cc/debug=notraceback lgi$callout_skey
$ exit
$!   
$ skeyinit:
$ cc skeyinit
$ exit
$!   
$ skeysubr:
$ cc skeysubr
$ exit
$!   
$ skeylogin:
$ cc skeylogin
$ exit
$!   
$ md4:
$ cc md4
$ exit 
$!   
$ put:
$ cc put
$ exit 
$!   
$ gethostname:
$ cc gethostname
$ exit
$!   
$ getskeyprompt:
$ cc getskeyprompt
$ exit
$!
$
$ skey:
$ cc skey
$ exit
$!   
$ test_skey:
$ cc test_skey
$ exit
$!   
$ all:
$ @compile CALLOUT_SKEY
$ @compile SKEYINIT
$ @compile SKEYSUBR
$ @compile SKEYLOGIN
$ @compile MD4
$ @compile PUT
$ @compile GETHOSTNAME
$ @compile GETSKEYPROMPT
$ @compile SKEY
$ @compile TEST_SKEY
$ exit
$!   

A.3.2. link.com

$ if P1 .eqs. "CALLOUT_SKEY" then goto callout_skey
$ if P1 .eqs. "SKEYINIT" then goto skeyinit
$ if P1 .eqs. "SKEY" then goto skey
$ if P1 .eqs. "TEST_SKEY" then goto test_skey
$ if P1 .eqs. "ALL" then goto all
$!
$ write sys$output "unknown module"
$ exit
$!   
$ callout_skey:
$ link/share=LGI$CALLOUT_SKEY/notraceback sys$input/opt
lgi$callout_skey.obj, skeylogin, skeysubr, md4, put, strftime,
sys$share:vaxcrtl
.exe/share
universal=lgi$loginout_callouts
$ exit
$!   
$ skeyinit:
$ link skeyinit, skeysubr, skeylogin, md4, put, gethostname,
sys$library:vaxcrtl
/lib
$ exit
$!   
$ skey:
$ link skey, skeysubr, skeylogin, md4, put, sys$library:vaxcrtl/lib
$ exit
$!   
$ test_skey:
$ link test_skey, skeysubr, skeylogin, md4, put, sys$library:vaxcrtl/lib
$ exit
$!
$
$ all:
$ @link CALLOUT_SKEY
$ @link SKEYINIT
$ @link SKEY
$ @link TEST_SKEY
$ exit

A.3.3. define.com

$ skeyinit :== "$dskb:[ccdanny.skey]skeyinit.exe"
$ skey :== "$dskb:[ccdanny.skey]skey.exe"

A.4. Data Files

A.4.1. SYS$MANAGER:SKEYKEYS.DAT;1

USER1 0099 ko492802 eb8fa6cded94a3f3 Oct 24,1993 USER2 0099 ko15288
aa2fb780f2383cc4 Oct 15,1993