Charon: Kerberos^{T M} Extensions For Authentication Over

Secondary Networks

by

Derek A. Atkins

Submitted to the Department of Electrical Engineering and Computer Science in partial fulfillment of the requirements for the degree of Bachelor of Science in Electrical Science and Engineering at the MASSACHUSETTS INSTITUTE OF TECHNOLOGY May 1993 \begin{matrix}c \\ ◯ \end{matrix} Derek A. Atkins, MCMXCIII. All rights reserved.

The author hereby grants to MIT permission to reproduce and to distribute copies of this thesis document in whole or in part, and to grant others the right to do so.

Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Department of Electrical Engineering and Computer Science May 7, 1993

Certified by . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Jeffrey I. Schiller Network Manager Thesis Supervisor

Accepted by . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Leonard A. Gould

Chairman, Departmental Committee on Graduate Students Charon: Kerberos^{T M} Extensions For Authentication Over Secondary

Networks

by

Derek A. Atkins

Submitted to the Department of Electrical Engineering and Computer Science on May 7, 1993, in partial fulfillment of the requirements for the degree of Bachelor of Science in Electrical Science and Engineering

Abstract

In this thesis, I describe extensions to the Kerberos Authentication System to enable a secure method of Authentication over multiple networks. Kerberos was designed with a fully-connected IP network in mind, however when you add dialup capabilities to the picture, Kerberos doesn’t expand to secure the whole connection.

Charon was created to tackle this problem. It was developed to provide a way to securely authenticate to a login server over a modem connection, without allowing a passive attacker to gain enough information to impersonate the user. This means that a user can log into a Kerberized host without typing his password in clear-text over the phone. In addition, no modifications to the login server’s base operating system need to be made in order to accomplish this.

Thesis Supervisor: Jeffrey I. Schiller Title: Network Manager

T M Kerberos is a trademark of the Massachusetts Institute of Technology

Charon: Kerberos^{T M} Extensions For Authentication Over Secondary

Networks

by

Derek A. Atkins

Submitted to the Department of Electrical Engineering and Computer Science on May 7, 1993, in partial fulfillment of the requirements for the degree of Bachelor of Science in Electrical Science and Engineering

Abstract

In this thesis, I describe extensions to the Kerberos Authentication System to enable a secure method of Authentication over multiple networks. Kerberos was designed with a fully-connected IP network in mind, however when you add dialup capabilities to the picture, Kerberos doesn’t expand to secure the whole connection.

Charon was created to tackle this problem. It was developed to provide a way to securely authenticate to a login server over a modem connection, without allowing a passive attacker to gain enough information to impersonate the user. This means that a user can log into a Kerberized host without typing his password in clear-text over the phone. In addition, no modifications to the login server’s base operating system need to be made in order to accomplish this.

Thesis Supervisor: Jeffrey I. Schiller Title: Network Manager

T M Kerberos is a trademark of the Massachusetts Institute of Technology

Contents

1 Introduction 1 1.1 Old security methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 The Kerberos model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3 The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

2 Design Goals of the Charon System 5 2.1 Security Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.2 Protocol Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.3 Secondary Kerberos Protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.4 The Wire Protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.5 Packets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.6 Information Packet Transfer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

3 Charon Implementation 13 3.1 Packet Ordering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.2 Control Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.3 The Dispatch Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.4 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

3.4.1 RPC module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.4.2 Parser module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.4.3 IA5 module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.4.4 Error module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.4.5 Kerberos Library Subset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.4.6 Charon module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.4.7 Kerberos module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

4 Application Interface and Examples 19 4.1 API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.1.1 Input/Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.1.2 Obtaining Tickets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4.1.3 Errors and Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4.2 Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.2.1 clogin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.2.2 ckinit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4.2.3 ctelnet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4.2.4 ckermit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

5 Testing Strategies 27 5.1 Testing the Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 5.2 Combined Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 5.3 Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

6 Conclusions 29 6.1 Future Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 6.2 Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

A Sources to the Charon Library 31 A.1 crn.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 A.2 crn err.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 A.3 ia5.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 A.4 krb.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 A.5 lib krb.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 A.6 parser.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 A.7 rpc.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

B Charon Library Include Files 73 B.1 charon.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 B.2 charon err.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 B.3 charon prot.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 B.4 crn.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 B.5 ia5.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 B.6 krb prot.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 B.7 parser.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 B.8 rpc.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 B.9 table.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 C Ckinit (src/ckinit) 87

C.1 ckinit.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 iv

List of Figures

1-1 Network Topology. Note that the bad guy has access to both the primary (ethernet)

and secondary (phone) networks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2-1 Proposed Protocol with multiple key-packets in a single transmission. . . . . . . . . 8 2-2 Interrealm Extensions to my proposed protocol. . . . . . . . . . . . . . . . . . . . . . 9 3-1 Module Dependency Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 vi

List of Tables

2.1 Packet Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.2 Version 2 information packet structure . . . . . . . . . . . . . . . . . . . . . . . . . . 12 3.1 Order of data transfer between client and server machines. . . . . . . . . . . . . . . . 14

Chapter 1

Introduction

Computer network security is inherently a problem of trust: in order to believe that a user is who he claims, you need to have some method to trust that user. At MIT/Athena, the Kerberos Authentication System ([6]) was developed as an authentication system. However, the MIT implementation of Kerberos was designed to work on a fully-connected IP network; it was not designed to work over multiple types of networks, such as a combination of LAN and phone-modem. This thesis describes Charon, created to secure secondary networks and provide Kerberos authentication over previously insecure networks.

The name Charon is derived from Greek mythology, where he was the ferryman to souls over the river Styx to Hades. The myth continues in that Charon requires a passenger to verify their identity with some token before he would ferry them safely across. Similarly, the Charon protocol expects a user to present a password as an identifying token before it safely will pass a Kerberos ticket across to from the client to login server. This will be described in more detail later.

The rest of this chapter discusses the status quo.

Chapter two describes the Charon security goals, protocol design, and assumptions.

Chapter three describes the implementation of the Charon library.

Chapter four describes the Charon API and applications.

Chapter five describes the testing that was done on the system.

Chapter six contains conclusions and possible future enhancements to the Charon system.

1.1 Old security methods

In trying to secure a computer network, one first has to make some assumptions about the network to be secured. In the first security model, users had to type their password over the network in the clear in order to log into a remote host. This means that anyone listening on the network can receive their transmissions and grab the password. An attacker can then retransmit this password at a future time to gain access to the user’s account.

The next step for security, on most UNIX^{T M} systems, is accomplished through the concept of trusted hosts and trusted users. What this means is that attempting to log on to a machine from a trusted host as a trusted user will be accepted by the machine without typing a password. In other words, a “trusted” machine, asserting a user’s identity, is assumed to be honest.

This is done, in the Berkeley UNIX style, by using hosts.equiv and .rhosts files. These files tell a machine which hosts it trusts, and which host/user pairs a user trusts. Therefore, if a user wants to be able to login to one host, A, from another, B, then they need to either have B in A’s hosts.equiv file, or have B in their own .rhosts file.

However this type of “security”, through machine trust, can cause problems. Since anyone who can become root on a host can then become any other user via the su command, they can then wander the network as this other user, following a path of machines that trust that host or account. In other words, not all machines can be assumed to be honest. Another problem of the old security methods is that if a user is not on a trusted machine then they still have to type their password in the clear across the network, which reverts to the old problem. Another problem is that if an attacker can spoof the name of her host to be that of a trusted host, they can again gain unauthorized access to other machines across the network.

1.2 The Kerberos model

These problems were addressed with the advent of Kerberos by MIT Project Athena. Kerberos is a network authentication system that solves many of the problems of standard UNIX security by removing the concept of a trusted host, and changing the concept of a trusted user. The concept of Kerberos is that everyone and everything, users, hosts, services, etc., has a name and a secret key. The only entities that know these secrets are the owner (i.e., the user or the service-providing machine) and the Kerberos Server. In other words, the key is a “shared secret” between the named entity and the Kerberos Server [2, 5].

The Kerberos Server, also called the Key Distribution Center (or KDC), has the job of issuing tickets to users. A ticket is a cryptographic block that a user can present to a service provider to prove the user’s identity. The authentication begins by the user telling the KDC their name. The KDC then sends the user a Ticket Granting Ticket (TGT), encrypted in the user’s key. The user then types their password on their workstation. The workstation translates this password into the user’s key, which is used to decrypt the TGT. The workstation can use the TGT to obtain other tickets for any services the user may wish to use.

If the user wants to log into a remote machine, for example, all she needs to do is obtain a TGT, which is secure because she is only typing her password to her local machine. The obtained TGT is used to request a service ticket for the host to which she wants to login. The service ticket is a cryptographic block, like the TGT, which is encrypted in the shared key between the service’s host and the KDC. The client then passes this service ticket to the login server and, if the credentials are valid, the user will get logged in without having to type anything in clear-text over the network. In addition, the user can optionally encrypt this session so that all data sent over the network between the remote host and their local host is encrypted.

T M UNIX is a trademark of Unix System Laboratories, Novell, or some such company

Kerberos makes a number of assumptions of the machines and network it wants to secure^{1}. One assumption is that all decryption of tickets is done locally on the client machine. This means that the client must have enough computing power to do a Data Encryption Standard (DES) [4] decryption in a reasonable amount of time. Another assumption in the MIT implementation of Kerberos is that a client must be able to speak directly to the Kerberos Server in order to obtain tickets. One reason for this is that the KDC uses the IP address of the client machine as part of the Kerberos ticket.

1.3 The Problem

However, now assume that there are two separate networks which are connected only though some intermediate host, for example a phone modem connected to a machine on the Internet. In this case I call the phone line and modem the “secondary network”. In this setup, using standard Kerberos, there is no way to secure the connection between the client machine and the server machine on either end of the modem connection.^{2} In this situation, given the standard Kerberos protocol, it is possible for an attacker to get enough information to actually log in as the user if the modem connection is monitored. An example of this network is in figure 1-1, which shows a client terminal, connected via modem to a server machine, which is on an ethernet with the KDC.

In this network, assuming that the server host is using Kerberos, an attacker can look at the secondary network, which is insecure, and grab the password or any data that crosses that line. In addition, she could also watch the primary network, the ethernet in this example, and grab the Kerberos packets. Then, using the user’s password, she can become that user and decode all the user’s data.^{3} What’s even worse is that this attacker now has enough information (the password) to impersonate that user on the network. The bad guy just needs to try to log into the machine and present the user’s password (which was conveniently grabbed earlier) at the prompt.

To protect against this kind of attack, it would be nice to extend the Kerberos protocol across the modem link in order to secure that section of the connection, so that a user can log into a host server Figure 1-1: Network Topology. Note that the bad guy has access to both the primary (ethernet) and secondary (phone) networks.

1see [6] for more details

2It is possible to run some sort of IP layer over the modem, such as SLIP, which would enable the client to talk directly to the Kerberos Server. If IP connectivity is available, then Charon would not be necessary. However, I am trying to deal with the case in which it is not possible to run such an environment.

3This attack requires that all the user’s data (Kerberos packets in particular) has been captured

from a client terminal without sending enough information for an attacker to use to gain access as that user. This system, described in the rest of this paper, is the Charon Protocol extensions to Kerberos over secondary networks.

Design Goals of the Charon

System

The first assumption Charon makes that that there can be two separate networks between the client machine and the KDC, connected by a host which I will call the login server. These two networks, the primary network and secondary network, communication with each other only through the login server. A “Secondary Network,” as I use the term, would include the phone network, if it connects via modem a terminal client and a login server, and the login server is on the Internet. We have to make a few assumptions of the situation which we are attempting to secure:

• Both networks are insecure. This means that an eavesdropper can listen to the network, as well as the modem connection, and read anything that is sent in clear text.

• The terminal does not have a way direct to communicate with the KDC to obtain Kerberos credentials.

• The terminal to host link may be slow^{1} and/or single threaded, like a serial connection or a modem.

2.1 Security Goals

The goals of this design is the create a protocol that is as secure as the standard Kerberos protocol. It should be quick, versatile, and simple to use and integrate into many applications. It should not be vulnerable to replay attacks. The security goals of this design are:

• To authenticate the user on the terminal to the login server using Kerberos.

1slow compared to a typical LAN speed

• To deposit a Kerberos TGT on the login server after authentication.

• Passwords, and any other information that would give a passive attacker an advantage, never cross the net, either primary or secondary, in clear text.

• To give the user the option to encrypt the session.

• To keep an attacker from getting enough information to become the user. This means that it should be secure against replay attacks, the attacker shouldn’t be able to obtain the users’ credentials, or trick the host using this protocol.

The protocol should defend against the same attacks that Kerberos does. For example, passive eavesdropping should not yield any information. It should guard against replay attacks, so that an eavesdropper can’t try to use someone else’s data and log in as them. Such an attack is comprised of an attacker monitoring the modem line, recording the transmissions from the client to the login server, and then replaying those transmissions as their own, in hope that the login server will believe that the attacker is who he says he is. It should also be safe against spoofing from the server host as well, so that it can’t trick the user into giving away their tickets to an attacker.

The protocol should also allow for optional encryption of the session after the Kerberos exchange takes place. Possibilities are no encryption, encrypting from client to server only, encrypting from server to client only, or two-way encryption.

It should also allow for the ability to re-authenticate, so that once connected the user can get another Kerberos ticket. This means that the protocol should be usable from a login shell, and shouldn’t require any kind of out-of-band connections. In other words, there needs to be some kind of program that runs on the server that should be self contained, and should use the terminal for input and output. This method was chosen so that no modifications to the login server’s operating system are necessary.

Moreover, users should be able to decrypt the Kerberos information offline, in case their terminal program doesn’t understand the Charon protocol. For example, a user should be able to capture the cryptographic information into a file, and then escape into another program which decrypts that file and creates a response, which can then be shipped across to the host.

2.2 Protocol Goals

The protocol design makes the following assumptions:

• Both networks can send at least six-bit data, which includes the IA5 subset of the ASCII character set.

• The terminal does not necessarily need to know about what kind of network the primary network is, and should get all relevant information it needs from the login server.

• The protocol should be extensible.

The protocol should work over connections that are not 8-bit clean, meaning that it shouldn’t depend upon being able to send 8-bit data, although it should send 8-bit data if the connection will allow it. I decided on a six bit protocol, which will allow data to traverse through protocol translators and other such devices or programs which might not pass some control sequences or other 8-bit data. This is a safety measure so that nothing in the data could possibly be an escape character in any piece of hardware along the link.

Since the client shouldn’t need to know about the server network, it doesn’t need to know about things like sockets, TCP/IP, hostnames or name resolution, Kerberos realms or servers, or anything else that a normal Kerberos client depends upon.

2.3 Secondary Kerberos Protocol

The secondary Kerberos protocol should be based, in part, on the normal Kerberos protocol. One possibility would be to just have the login server pass Kerberos packets from the secondary network onto the primary network, and just use the standard protocol as it was originally designed. While this solution works, its not very elegant, because it requires multiple round-trips for the data. Extra round trips can mean a longer delay (120 bytes will take one second to transfer on a 1200 baud modem). By making the protocol into a single series of exchanges, instead of the standard Kerberos exchange, it becomes possible to easily create an offline application program so you don’t have to modify the communications program resident on the client computer.

Normally, to get a secure connection between a client and a server, the client would get a TGT, and then request a service ticket for the server. In other words, we require at least two tickets on the client.^{2} It is this requirement of multiple tickets that would force the multiple round trips using the standard Kerberos protocol.

To reduce the number of round trips, we can request two tickets together, the TGT and a service ticket, and send both of them to the client at the same time. Then the user can decrypt them together. The client then can use the service ticket for a secure connection to the server host, and send the TGT. In order to make sure that it is really the user requesting the tickets, both the TGT and service tickets are encrypted in the secret key of the user, K_{u}. Graphically, this exchange is depicted in figure 2-1.

2In Kerberos version 4, we require exactly two or three tickets on the client, depending on whether the user principal and server are in the same realm or not.

We have to be careful with replay attacks. An attacker could watch the modem connection, grab the encrypted TGT, and then replay it to the server and gain access to the user’s account. This is supposed to be prevented in Kerberos by remembering the authenticators, and the Internet (IP) Address of the host is a part of that identity.^{3} However in a dialup situation, or on a secondary network, the client machine doesn’t have an IP address for use in the authenticator. Therefore, we need some other form of replay protection, since Kerberos version 4 doesn’t have a working replay cache.

The cookie in this protocol is the protection against replay attacks. It is a piece of data, guaranteed unique, which is created by the login server and used to prove the session identity. The server sends the cookie to the client with the initial tickets, and it expects the client to send the same cookie back to the server, encrypted in the session key from the service ticket. This means that it requires knowledge of the shared key between the client and server machines, which a passive attacker cannot know. To be unique, the cookie should be some function of the time and username, so that it is known to be unique. This implementation just uses the time, and makes sure it is usique by locking out any other process from grabbing a cookie until this one has it.

Another problem we have to watch is time skew. Since the terminal may not be synchronized to the host, the time of the client may not help prevent replays.

In the protocol exchanges, the term u refers to the user’s name, tgs is the ticket granting service’s name. The entity represented by ws is the login server. Also, I use K_{u} to represent the user’s secret key, and K_{x,y} to represent the shared key between the entities x and y.

Step Direction  Data
1  Host ⇒ KDC u, tgs, ws
2  Host ⇐ KDC {T_{u,tgs}, K_{u,tgs}}K_{u},
{T_{u,ws}, K_{u,ws}}K_{u}
3 Terminal ⇐ Host {T_{u,tgs}, K_{u,tgs}}K_{u},
{T_{u,ws}, K_{u,ws}}K_{u},
COOKIE
4 Terminal ⇒ Host T_{u,ws}, A_{u,ws},
{T_{u,tgs}, K_{u,tgs}}K_{u,ws},
{COOKIE}K_{u,ws}

Ticket T_{p,s} = {p, s, addr_{ws}, time, life, K_{p,s}}K_{s}
Authenticator A_{p,s} = {p, addr_{ws}, time}K_{p,s}
COOKIE A random value created by the host to protect against replays.

Figure 2-1: Proposed Protocol with multiple key-packets in a single transmission.

3Timestamps, replay caches, and/or challenge-response schemes are also in use.

Step Direction  Data
1  Host ⇒ KDC u, tgs, rtgs
2  Host ⇐ KDC {T_{u,tgs}, K_{u,tgs}}K_{u},

{T_{u,rtgs}, K_{u,rtgs}}K_{u}

3 Terminal ⇐ Host {T_{u,tgs}, K_{u,tgs}}K_{u},

{T_{u,rtgs}, K_{u,rtgs}}K_{u}

4 Terminal ⇒ Host u, ws, T_{u,rtgs}, A_{u,rtgs}
5  Host ⇒ RKDC u, ws, T_{u,rtgs}, A_{u,rtgs}
6  Host ⇐ RKDC {T_{u,ws}, K_{u,ws}}K_{u,rtgs}
7 Terminal ⇐ Host {T_{u,ws}, K_{u,ws}}K_{u,rtgs},
COOKIE
8 Terminal ⇒ Host T_{u,ws}, A_{u,ws},

{T_{u,tgs}, K_{u,tgs}}K_{u,ws}, {COOKIE}K_{u,ws}

Ticket T_{p,s} = {p, s, addr_{ws}, time, life, K_{p,s}}K_{s}
Authenticator A_{p,s} = {p, addr_{ws}, time}K_{p,s}
COOKIE A random value created by the host to protect against replays.

Figure 2-2: Interrealm Extensions to my proposed protocol.

One drawback of this proposed protocol is that for inter-realm authentication the requests are different. This means that if the realm of the user’s principal and the realm of the login server are not the same, the protocol has to request a different initial ticket set for the user to decrypt. Therefore, the login server needs to know whether or not to obtain the rcmd service ticket or the shared key service ticket, and the client needs to know to make the request for the rcmd ticket if it is an inter-realm exchange.

In addition, inter-realm authentication means that we have a problem with any offline decryption program, because there is an extra round trip for the cryptographic data required. However, if a terminal emulator, like kermit, has had Charon integrated into it, then this problem goes away, since the application can deal with the whole protocol, including the inter-realm exchange. This protocol is depicted in figure 2-2.

2.4 The Wire Protocol

Although I took some of the ideas for my protocol from rkinit [1], the Charon protocol has been enhanced to meet the requirements of the assumptions placed upon it. Like its predecessor, data is sent in packetized units. A packet consists of a “type byte”, a length, and the data. Theoretically, there is no limit on the size of a packet, but to be practical, it is limited to the size of an integer, which is assumed to be 32 bits. Also, it helps to remember that the transfer rate will move 120 bytes of data in one second at 1200 baud. See section 2.5 and table 2.1 for more detailed information on packets.

All data should be in the form of octet strings. Non-string data should be converted to network byte order, which means the most significant byte is first, before being sent over the wire. This is done so that different architectures can share data even if they represent numbers differently.

In addition, before the raw data is transferred, it should be converted to the IA5 alphabet in the encoding scheme described in RFC 1421 [3]. This encoding takes a block of 1-3 bytes of data, and converts it, always, to 4 bytes of encoded data. See appendix A.3 for more information on the IA5 encoding scheme.

The packet is kept separated in its three components, and each component is sent on a line by itself. A line always ends with a carriage-return, newline combination.

The first part of the packet is the “type byte,” which is really only six bits long. It is encoded into a single ASCII (IA5) byte, rather than all four, and that is sent alone across the wire.

Second, the length is encoded. The length is that of the octet-string, not the IA5 encoded string. The least significant 1, 2, or 4 bytes^{4} of the length is encoded and sent. The smallest number of bytes, between 1, 2, and 4, necessary to send the value should be used. Also, since an IA5 encoding will always produce 4 byte blocks, the encoded length will always have either four or eight bytes of data.^{5}

Lastly, the data is converted to IA5, and broken into lines of 64 bytes each. This data, like the length, will always have a length of some multiple of four. It is broken into separate lines because some applications may have limited line lengths. The number 64 was chosen somewhat arbitrarily, however it is a multiple of four and a power of two.^{6} Actually, I chose it so that I could easily use X’s cut-and-paste feature to test my code.

2.5 Packets

A packet is a unit of data that is sent across the wire from one side to the other. As mentioned, a packet consists of a type byte, length, and data. The type byte is used to specify what the packet contains. This allows the protocol to be event oriented in that it can expect one of a number of different packet types at a particular point in time, and execute different functions given the type of packet received. A packet is defined in table 2.1.

Each packet type has a special usage, all of which is enumerated below. Currently, less than half of the available packet types are in use, but this may change as the protocol develops in the future.

4since an integer is assumed to be 32 bits, then “4” bytes refers to the whole value

5I could have limited the lengths to 24 bits, which correspond to a single four byte IA5 block, however I decided that flexibility for future protocol additions was more important.

6Also, 64 is less than 80, which is the width of standard xterms, ttys, and the VM virtual punch card.

Packet  := Type Length Data
Type  := byte (llttttuu)
Length  := byte[0] … byte[Llength - 1]
Data  := byte[0] … byte[Length-1]
byte  := msb … lsb
Llength(ll) :=
ll = 00  := Llength = 1
ll = 01  := Llength = 2
ll = 10  := Llength = 4
ll = 11  := Undefined
Types (tttt) :=

0x0 Status

0x1 Client Version 0x2 Server Version 0x3 Information Packet 0x4 Server Kerberos Data 0x5 Client Kerberos Data 0x6 Client Authentication Data

Table 2.1: Packet Definitions

0x0 Status Used to relay status of the last command from one side to the other. These can be sent at any time.

0x1 Client Version The packet contains the client version number. When the server gets this, it will choose the lower of this value and its own version number to elect the protocol version number to use.

0x2 Server Version The packet contains the server version number. When the client gets this, it will choose the lower of this number and its own version number to elect the protocol version to use.

0x3 Information Structure This packet contains information from the server. This is described in section 2.6.

0x4 Server Kerberos Data This is used when the login server sends Kerberos data to the client.

0x5 Client Kerberos Data This is for the client to send Kerberos data to the login server, for which the server should forward to the KDC directly.

0x6 Client Authentication Data This is for the client to send its authenticator, TGT, and encrypted cookie to the login server.

2.6 Information Packet Transfer

Since the method of transfer has been determined, the next step was to decide what kind of information will be transfered between the client and server.

The first set of data that needs to be exchanged is version information. In particular, the client needs to know what version of the protocol the server is running, and the server needs to know what version the client is running. If a bad version number is given, then the protocol should signal an error and break contact.

In addition to the cryptographic transfers, which were discussed in section 2.3, the client requires an information packet, which consists of the principal, instance, and realm of the user, the host’s “instance” and realm, the server’s IP address (which is just in the form of a 32-bit number, so no knowledge of IP is really required), an encryption byte, and the cookie. In future versions of the protocol, such as when Kerberos version 5 is widely deployed, the address could be a krb5 address string, instead of an IP address.

The current version of the protocol (version 2) transfers this data in a single packet. The packet data has the form depicted in table 2.2. This packet can be parsed and unparsed because the data at the beginning is of a constant length, and the data at the end are NULL-terminated strings. This packet is sent from the login server to the terminal.

Bits Value

                              0...3  cookie
                              4...8  IP address
                                  9  encryption bytes
                              10...  principal (nul)

                                     instance (nul)
                                     realm (nul)
                                     host (nul)
                                     host realm (nul)

Table 2.2: Version 2 information packet structure

Charon Implementation

The majority of the Charon system is inside a single library. This library has two application entry points, and a number of call-back routines for a user to specify the input/output routines for the library to use.

The library entry points and application calling conventions will be discussed in more detail in the next chapter. Here I will only discuss the library design and implementation – details which are totally internal to the library itself.

3.1 Packet Ordering

The packet types, mentioned in section 2.5, are sent in a particular order to comprise the Charon protocol. A status packet can be sent at any time, and a status of anything other than zero will end the protocol. This allows for either side to break contact at any point if an error occurs.

The version 2 Charon protocol is specified in table 3.1. Each line in the definition is a block of data that needs to be sent. Each block of data may be composed of multiple packets. For example, the block entitled “server Kerberos data” actually consist of two packets, one for the TGT, and one for the service ticket. More on this later.

Table 3.1 includes both the inter-realm protocol and the local-realm protocol. The only difference between them is the data exchange in the middle, where the client sends Kerberos data to the login server and the server replies with the “rcmd” service key. This key is that used by Kerberos for remote commands.

3.2 Control Loop

In order to realize the packet protocol, I developed a dispatch table that allows a compile-time table to be generated. The dispatch loop is event driven. When it enters a state, it sees if there is a

Data From Client  Data From Server
Client Version ⇒
⇐ Server Version
⇐ Information Packet
⇐ Server Kerberos Data
Client Kerberos Data ⇒
⇐ Server Kerberos Data
Client Authentication Data ⇒
⇐ Status

Table 3.1: Order of data transfer between client and server machines.

precondition on the state, and if so it runs the specified function. Then it waits for a packet to come in.

When the packet arrives, the loop will dispatch to the appropriate routine for the packet type that arrived, and then update the state. This continues until the protocol has finished or an error occurs (an error can either be locally, or an error status from the other side).

3.3 The Dispatch Table

The dispatch table was designed in order to facilitate the development of the protocol, and to realize the extensibility design goal. By making the protocol event driven, it would be simple to update the protocol at a future date, while keeping backwards compatibility, without having to modify much of the existing code.

A table is a multi-dimensional array, with indices for the mode (client or server), version number, and state. The protocol assumes that the first state (state zero) for any version is the client/server version exchange. Other than that, different versions of the protocol can differ in any way.

The design goal for this section was to make it simple to create newer protocols when appropriate, and make it simple for a programmer to update the code. In addition, I wanted to make it simple for the code to choose which version of the protocol to execute. I decided that a dispatch table would be the most robust approach to meet these goals.

3.4 Modules

The library is broken up into a number of different modules. Each module is supposed to handle a set of functions in a single area. The different modules, each of which will be described later, are the rpc module, the parser module, the ia5 module, the charon module, an error module, the Kerberos module, and a Kerberos library subset module.

Each module is as self contained as possible, although there might be some inter-dependencies.

By making self-contained modules, it made it easier to test. For example, I could test the IA5 code without needing any other part of the code at all.

A module dependency diagram in shown in figure 3-1.

Figure 3-1: Module Dependency Diagram

3.4.1 RPC module

The RPC module is in charge of sending and receiving data. It has a number of functions which an application can use to set some callback procedures, which are used for the actual IO. (These functions are defined in more detail in section 4.1.) If none of the callbacks are set, then the RPC module will use the ANSI standard IO functions.

This module deals with data as packets. A calling function will ask to send a packet, and give the length of the packet and the packet type. This module will send the packet type, then the length, and then the data, as discussed in section 2.4. It is this module that implements that separation.

All data is assumed to be binary. Before it is sent, it is converted to the IA5 alphabet, broken down into lines, and then sent. This work is done mostly in one function:

   int crn send data(u char *data, u int datalen, u char datatype)

In addition to sending data, the RPC module also reads data. There are two ways that it can read data: the first is to place it into a pre-existing buffer, and the second is to create a new buffer for the data. To see this through, there are two functions called by other modules. The first one will wait for data to come in and will create a buffer for the data:

   int crn wait for data(u char **data, u int *datalen, u char *datatype)

and the second expects to be able to put the data into an existing buffer:

   int crn read data(u char *data, u int *datalen, u char *datatype)

Both of these functions require that the input function block for data, or at least only return with valid data.

The RPC module also has a number of special functions which are used to send special data. Basically, a caller passes in a data type, and this process will call the rpc functions to send or read that particular data type. Examples of this type of function include:

   int crn rpc getdat(MSG DAT *data)
   int crn rpc senddat(MSG DAT *data)
   int crn get status()

3.4.2 Parser module

It is the job of the parser module to convert between internal structures and octet strings, for use in sending using the RPC library. For example, the parser contains procedures to convert between integers and octets, which currently assume 32-bit integers.

In addition, the parser module contains the code to create the information packet that gets sent from the login server to the client terminal:

   int info2octet(charon t *charon, u char **data, u int *datalen)

This function takes the internal structure and, depending on the protocol version in use, will create the appropriate data string to send to the client.

The client has an equivalent function, which does the exact reverse operation:

   int octet2info(u char *data, charon t *charon)

The calling function must supply the buffer space for the encoded or decoded data, and the encoded or decoded data length should hold the length of that buffer before it is passed into the functions.

3.4.3 IA5 module

IA5 is the International Alphabet Five, a set of 64 ASCII characters that are used to represent six-bit values. The design specified a six-bit clear network, and IA5 was chosen as the representation of all over-wire traffic. These functions convert buffers only, and do not do any line breaking. The rpc module is in charge of breaking the encoded string into lines and reconnecting them upon reception.

The IA5 module implements two functions which are used to convert binary data to an IA5 encoded string, and vice-versa:

   int ia5 encode data(u char *data, u int datalen, char *encoded, u int *encodedlen)
   int ia5 decode data(char *data, u int datalen, u char *decoded, u int *decodedlen)

3.4.4 Error module

This simple module will convert an error number to a printable string that describes what the error is. The returned value is static storage inside the module. The function is called:

   char *crn err msg(int error)

3.4.5 Kerberos Library Subset

The Kerberos library was developed with certain goals in mind, and the implementation has its own breakdown of functionality. However, in order to obtain some of the functionality of Charon, it was necessary to break two particular abstractions in the Kerberos library. These two broken functions are in this module.

The first of these two functions was created to store a ticket in the ticket cache:

   int crn store ticket(charon t *charon, int ticket, int init cache)

It takes one argument, ticket, to figure out which ticket in the charon structure to store and uses the other argument, init cache, to know whether or not it needs to initialize the ticket cache file. This was needed for the client, since the server doesn’t send a full KDC response. Rather, the client only gets cyphertext of the ticket. This, however, does contain enough information as far as Kerberos is concerned.

The second function was written because we can’t use the Kerberos function send to kdc. However, in order to obtain the appropriate ticket in the case of an inter-realm authentication, it is necessary for the client to create the packet to send the to remote KDC (see figure 2-2). Therefore, this function was created to obtain the rcmd ticket:

   int crn get rcmd tkt(charon t *charon)

3.4.6 Charon module

This module contains all the code to handle the Charon protocol. There is one function, the Charon main loop, which will call these functions based upon the packet type received. The protocol is started, for all intents and purposes, by calling the function

   int crn main loop(charon t *charon)

This is the main loop, and will send and receive packets as per the version of the protocol in use. It will then call other procedures to process the different types of packets. ProcessVersion,

ProcessInfo, ProcessClientKDC, and ProcessCliAuth are examples.

3.4.7 Kerberos module

This module contains procedures for Kerberos protocol exchanges, terminal I/O, password fetching, and code to request the initial kerberos tickets. Unlike the Kerberos library subset, this code deals specifically with calls into the Kerberos library that Charon requires, rather than providing the procedural functionality that Kerberos lacks.

The login server needs to send the client terminal two tickets to decrypt: the TGT and some service ticket (either rcmd or the shared key, depending on local- or inter-realm authentication). It does this by using the Kerberos library callback krb get in tkt. In fact, the login server uses this twice (once per ticket).

Once the two tickets are obtained, it will enter the Charon main loop and start the communication.

Application Interface and

Examples

This chapter discusses the Applications Programmer Interface (API) of the Charon Library, in depth. It provides examples of applications that use the Charon library, and then it describes modifications to existing programs to use the authentication protocol of Charon.

4.1 API

The API for the Charon library contains a number of functions to set the callbacks for IO functions and to actually start the Charon protocol.

4.1.1 Input/Output

The first set of functions can be used to set the input and output file streams if none of the I/O functions are set:

void crn set input file(FILE *file)
     Requires:  file be a valid file, open for reading
     Modifies:  InputFile
     Effects:  Sets the input file to ``file''.
void crn set output file(FILE *file)
     Requires:  file be a valid file, open for writing
     Modifies:  OutputFile
     Effects:  Sets the output file to ``file''.

This next set of functions are used for an application to set callback functions for input and output. There are various modes of input and output, and I tried to create callbacks for as many different modes as I thought applicable. The different modes are I/O by character and I/O by line:

void crn set putc fcn(int (*fcn)())
Requires:  fcn be a function that puts a single character to a
file, of the form:
int fcn(char c)
    Effects:  Will output character `c' to the appropriate
    device.
Modifies:  Putc
Effects:  Sets the Putc function.
void crn set getc fcn(int (*fcn)())
Requires:  fcn be a function that gets a single character from a
file, of the form:
int fcn()
    Requires:  EOF condition is ignored
    Effects:  Will block until a character is available and
    will return a single character.
Modifies:  Getc
Effects:  Sets the Getc function.
void crn set puts fcn(int (*fcn)())
Requires:  fcn be of form:
int fcn(char *s, int len)
    Requires:  `s' contain at least `len' characters.
    Effects:  Will output the line held by `s' of length
    `len' to the appropriate output device.  Will always output
    `len' bytes.
Modifies:  Puts
Effects:  Sets the Puts function.  This is for kermit compatibility.
void crn set fputs fcn(int (*fcn)())
Requires:  fcn be a function that puts a line of n chars to the
file, and includes a CR/LF at the end.  fcn is of the form:
char *fcn(char *line, FILE *stream)
    Requires:  stream be a file opened for writing.
    Effects:  Output `line' to the file `stream'.

Modifies:  FPuts
Effects:  Sets the FPuts function.
void crn set fgets fcn(int (*fcn)())
Requires:  fcn be a function that gets a line from a file, ignoring
training CR/LF's, of the form:
char *fcn(char *data, int space, FILE *stream)
    Requires:  stream be a file opened for reading and not have
    an EOF condition.
    Effects:  Get a line of `space-1' characters or until a
    newline is reached, and store the data in `data'.
Modifies:  FGets
Effects:  Sets the FGets function.

In addition to just setting the files and callback routines, it is also necessary for the login server application to turn off echoing before starting the server get ticket function. Therefore, two functions were created to turn off echoing, and to revert the terminal to the state before echoing was turned off:

void *crn kill echo(int fd)
Effects:  turns off echoing on the file descriptor fd.  Returns a
state structure that should be used to revert the echo.
void crn revert echo(void *state)
Effects:  reverts the tty echo `state'.  This will only work once
on a given state.  Once a state is reverted, it cannot be used again.

4.1.2 Obtaining Tickets

There are two functions to obtain tickets. One function is for the server, and the other is the client. Once an application gets all the callbacks set up, it needs only to call the appropriate function to initiate the Charon protocol and obtain tickets.

int crn srv get tickets(char *principal, char *instance, char *realm, char *hostrealm,
int lifetime, char *ticketfile, int version, char *enc ses, des cblock *key)
Requires:  Echo-ing not be a problem (meaning it should
be turned off), and that the process be owned by
the uid who will own the tickets, and also be able
to read the srvtab file.  This can be done by having
the process's real id be that of the ticket owner, and
the effective uid be root.  In any event, being able to

read the srvtab is more important than owning the tickets.
Modifies:  key, enc ses
Effects:  Will get kerberos tickets for principal and instance
in the specified realm, and put them in the specified
file.  If the user doesn't exist, can't store the file,
or this machine doesn't have an rcmd ticket, then
fail with an error to the user.  Key is a pointer
to the user-defined space to hold a session key, and the
session key will be stored there if key is not NULL.
enc ses is a pointer to a character that will be sent
to the client, and it will contain the binary AND of
the server and client encrypt session values upon
return.  A NULL pointer is equivalent to no encryption
information, and nothing will be placed here.
The ticket will be stored in the default location.
Returns CRN OK if everything goes ok, and the ticket is
valid, and all check pass.

int crn cli get tickets(int version, int (*key proc)(), char *enc ses, des cblock *key)

Requires:  Echo-ing *not* be a problem (or turned off)
Modifies:  key, enc ses
Effects:  Will talk to the server and get the TGT data,
decrypt it, and then encrypt the ticket in the
rcmd of the server and send it back.  Will inform
the user if any errors occur.
key proc is a pointer to a user-defined procedure, expecting
arguments:  (user, instance, realm, arg, key).  If it is
NULL, then it will use a pre-defined function to get
the user's password and create a DES key.
key is a pointer to the user-defined space to hold a
des key, and the session key will be stored there if
key is not NULL.
enc ses is a pointer to an encrypt session value.  It
will be sent across to the server, and upon exit will
contain the binary AND of the server and client bytes.
If it is a NULL pointer, no encryption is assumed.
Returns CRN OK if everything goes ok.

4.1.3 Errors and Debugging

A function is needed to convert an error number to a printable string, so that a user can understand what an error means:

char *crn err msg(int error)
     Effects:  Returns a string that explains the error we received

There is also one function for debugging purposes. This function takes a filename and will open up a file to write error messages:

void crn rpc set debug file(char *file)
     Modifies:  ErrorFile, charon debug
     Effects:  Tries to open file for write and will set ErrorFile to
     that file if possible.  Also sets charon debug if successful.

4.2 Applications

The rest of this chapter will discuss a number of applications that I have written or modified to use the Charon protocol. The code for these programs are in the appendices along with the Charon library code. Since the majority of all the work for Charon is done in the library itself, you will notice that these programs are fairly simple, or the changes are minimal. The only thing they do is process commands to set up the necessary state to begin the protocol.

4.2.1 clogin

This program is supposed to let a user login using the Charon protocol for password verification. It is supposed to be run as a shell for a special user that has the same privileges as root. Basically, this program is setup to be the shell of a special user, say “krblogin.”

When a user logs in as this special user, this program is run. This program will prompt the user for their real username, and then it will enter the Charon protocol and wait for the client to connect.

Since the Charon library only returns without error when the TGT is successfully obtained (which means that the password was correct), we can use that return value to know that the user successfully typed in their password.

If this is the case, then we have two options. The first option is to emulate the login program itself, add the user to the wtmp and utmp files, add them to the password file^{1} and start the user’s shell.

1This assumes that you are using the Hesiod system of Project Athena, where users are not in /etc/passwd by default, but rather they get added to the password file when they log in.

The second option is to execute some login program that will do all that work, such as the equivalent of login.krb from the Kerberos distribution. Currently, I have implemented the first of these two options; it is not however a portable solution, and it would probably be a better solution to use the second of these two options.

4.2.2 ckinit

This program will execute a Charon Initialization. It is run on the login server, after a session has already been instantiated, to obtain a new set of Kerberos tickets. The user supplies a set of options to the program by using command-line flags.

If the program is called without any arguments, it will check the local Kerberos realm for the machine’s Kerberos realm, it will check the current ticket cache for the principal, instance, and realm of the user, and, if there are not current tickets, it will get the user’s name from the password file and use that.

Optionally, the user can use the command-line options to the program. For example, the “-p” option can be used to set the Kerberos principal (of the form principal.instance@realm). This will override anything that might be in the current ticket cache or the password file. In addition, the user can override the Kerberos realm by using the “-k” option. This will override the default realm.

Tickets normally get stored in the file pointed to by the environment variable KRBTKFILE. However, a user can use the “-f” flag to set a different filename in which to store the tickets.

And finally, the “-l” flag is used to set the lifetime of the tickets. The option to this flag should be the length of time, in minutes, for the ticket to last. If this option is not given, then the default lifetime will be used.

4.2.3 ctelnet

The ctelnet program is one of the client programs modified to use the Charon protocol. It was based upon the code from ktelnet, a Kerberized version of telnet. The Charon protocol was added by creating a new command within the telnet program also called ckinit. This “subprogram” within telnet can be executed by escaping back to the telnet> prompt and typing ckinit. This will initiate the client side of the Charon protocol.

What this will do is use the telnet connection to send data as a Charon client, so when the user connects to a server and tries to use the protocol, they can escape back to the local system and use this telnet command to execute the protocol.

4.2.4 ckermit

Like the ctelnet program, kermit was modified to also have the ckinit command embedded inside it. It works in the same manner as the telnet client: A user escapes back to the local machine to start the client Charon protocol.

26

Testing Strategies

The code was written using a combination of bottom-up and top-down implementation styles. This way it would be possible to test sections of the code as early as possible and fix any bugs that may have existed before the whole library was connected, since more modules make debugging a difficult task.

The majority of the testing and debugging was done inside xsaber, a C interpreter distributed by Centerline Systems. I used this debugger mostly because it helps find type errors in C, and finds stylistic errors which may lead to problems.

5.1 Testing the Modules

One advantage of a bottom-up implementation is that you can start testing modules right away, since everything that that module depends on has already been implemented. As such, the first modules to be tested were the ia5 module and parser module.

Since xsaber is an interpreter, it was possible to create a program scenario on the spot and see how the implementation executes. For example, in order to test the ia5 module, I created two buffers. One buffer was for the binary data, and the other buffer was for the encoded data. I then created different lengths of random data and encoded the data, and then decoded the data and compared the results.

I tested the parser module in a similar manner, creating test cases, encoding and decoding the test data, and comparing the results by hand. Originally I had a bug in parsing and unparsing the info structure, however that was a problem of pointer arithmatic and was quickly fixed.

5.2 Combined Testing

The next step in testing the code was to combine the system together and try to use it. This was done by having two xsabers running and use the cut-and-paste feature of the X window system to move data from one process to the other.

Some of the bugs I found involved breaking the encoded data into lines, lengths not being correct, and the bug in the parser code that was mentioned previously. In addition, I also found that I had to ignore trailing white-space.

At this point, I was successfully able to obtain a Kerberos ticket for myself. The next test I tried was to obtain an inter-realm ticket. The problem here is that the send to kdc function in the Kerberos library, as of this writing, does not set the size of the returned packet in the KTEXT structure. This means that a function that calls send to kdc has no way of knowing how much data was received from the KDC.

However, inter-realm authentication requires that the login server send a packet to the remote KDC and then return that packet to the client machine. Therefore, if the send to kdc call returns successfully, I check the length of the packet. If that length is zero, then I automatically set it to MAX KTXT LEN, which is the maximum length of a KTEXT packet, as defined in the Kerberos header file.

5.3 Integration Testing

The next step was to test ckinit and the modified versions of kermit and telnet. When I started testing this, I found that there was a bug in my line-breaking code. It was just an error in arithmatic, but it was sending the wrong number of characters, so the protocol was failing.

The reason I didn’t see this bug before was because now it was using the callback functions, outputting a single character at a time, rather than using fputs to put the line, which just went to the NULL character.

I was able to track the problem down by adding debugging lines that wrote all data transfers to a file, so I could peruse the output in more detail than on a terminal. Even if this problem did exist when I was testing in xsaber, I wouldn’t have had a problem because a NULL is a non-printable character, so cut-and-paste wouldn’t have grabbed it.

Once that bug was fixed, ckermit and ctelnet worked properly, and would obtain tickets.

Conclusions

The conception of the Charon protocol was fairly simple, yet any computer security development inherently contains pitfalls that must be avoided. Because of some assumptions made in the design of the Charon system, extra care was needed to keep the protocol secure. Therefore, it was neccessary to try to think of any possible attack on the system while it was being designed.

However, the ideas that surfaced in the conception of Charon have been floating around for some time. While the difficulty of the concept was fairly minimal, doing it right takes a lot more talent.

The design of the dispatch table took a lot of thought, and actually went through a number of stages of development. It probably could have been done a lot cleaner than my final version, however I thought it was clean enough to be understandable and easily maintained and modified. A lot of the thought was how to actually define the table in an easy manner.

I have come to the conclusion that most communication programs don’t give the programmer direct access to character input/output routines. Although this is a minor detail, it was annoying spending hours trying to understand the telnet and kermit code in order to add Charon functionality.

6.1 Future Work

There are a number of possible steps that can be built on top of the Charon system. For one, I don’t do any type of encryption of the link once the authentication is made. I tried to allow for encrypting the link with the encryption session byte in the transfer, however nothing is currently done to use that byte.

This means that some application could be written that actually used that byte and the DES key to encrypt the link once the initial TGT is obtained. This would require the programs on both ends to understand the encryption being used.

I also left the way open to optionally encrypt only certain parts of the session. For example, a session could only encrypt the data from the client to login server, but not the other way. This is one possible use of the session byte.

Also, I have not implemented either the offline client application nor a login server shell that sits between /bin/login and login.krb. This, too, is something that can easily follow from this thesis.

Charon, as implemented here, is based entirely on Kerberos Version 4. However, much of the work here could be used with Kerberos Version 5. That would require creating a new protocol version, changing some of the parsing routines, and modifying some of the internal Charon routines and structures.

6.2 Acknowledgements

I would like to acknowledge the following people for their help, encouragement, or work that went into this Thesis. It couldn’t have happened without them.

Estelle Abrams, Dede and Don Atkins, Ruth Atkins, Richard Basch, Matt Braun, John Carr, Calvin Clark, Mark Eichin, Neil Haller, Ping Huang, Jon Rochlis, Jeff Schiller, Bill Sommerfeld, Theodore T’so, Stan Zanarotti.

Appendix A

Sources to the Charon Library

A.1 crn.c

/* crn.c −− This file contains the code to do the state machine for

* the Charon protocol. All the state code should be done in this

* file, and it should call other functions depending on the state,

* incoming packet type, and application type. I was considering making

* it fairly table−driven, as best as possible. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/crn.c,v $

* $Author: warlord $  10

* */

#if !defined(lint) && !defined(SABER)

static char rcsid crn c[] = "$Id:  crn.c,v 1.7 93/05/05 11:54:32 warlord Exp $";

#endif

#include <com err.h> #include <krb err.h>

#include <charon prot.h>  20

#include <charon err.h> #include <rpc.h> #include <parser.h>

#include "table.h"

static char errbuf[BUFSIZ];

/* This loop is the main dispatch loop of the Charon system. It

* called by both the client and server side, and should most likely

* be table driven.  30

*

* Effects: Uses the mode (CLIENT MODE or SERVER MODE) to know what

* to expect from the other side, and what it should do.

* Uses the version passed in to know which protocol to

* try to use. The first thing ALL protocols do is

* exchange version info, and they will chose the minimum

* of the two possible versions. */ int

crn main loop(charon t *charon)  40

{ int state = 0; /* state of the machine */ int retval; /* return values from functions */ int state changed = 1; /* did we change state this loop? */ int maxstate; /* highest state in table */ u char pkt; int pkt type; u char *data; u int datalen;

v table version table = command table[charon−>mode];  50

d table dispatch table;

/* Lets go into the table. We will keep going while retval == 0

* and we have a state to go to. */

if (version table == NULL) return (CRN BAD VERSION);

do {  60

/* The dispatch table can change if the version number

* changes. It should ONLY change when processing the

* version number */ if (charon−>version > MAX VERSION || charon−>version < MIN VERSION) { retval = CRN BAD VERSION; break; }

dispatch table = version table[charon−>version];  70

/* Make sure that this is a valid version table! */ if (dispatch table == NULL) { retval = CRN BAD VERSION; break; }

/* Just the maxstate for this table. */ maxstate = dispatch table[0].maxstate;

80

/* See if we changed state and if we have a function to enter

* the state. If both are true, then call that function. */ if (dispatch table[state].entry != NULL && state changed) (dispatch table[state].entry)(charon);

/* Wait for input */ retval = crn wait for data(&data, &datalen, &pkt); pkt type = pkt;

if (retval != CRN OK)  90

break;

charon−>data = data; charon−>datalen = datalen;

/* Make sure we have a function for this packet type, and if

* so, then call it! If not, then set retval = Bad Packet and break

* from this loop. */

if (dispatch table[state].fcn[pkt type] != NULL)  100

retval = (dispatch table[state].fcn[pkt type])(charon); else { retval = CRN UNKNOWN PKT TYPE; break; }

/* Free this up, if there was anything. */ if (charon−>datalen) free(charon−>data);

110

/* Get the new state, if we were ok */ if (retval == CRN OK) { retval = dispatch table[state].next state[pkt type]; state changed = retval − state; state = retval; retval = CRN OK; }

} while (retval == CRN OK && state >=0 && state <= maxstate);

120

return(retval); }

static int CheckStatus(charon t *charon) { return(crn read status((char *)charon−>data, charon−>datalen)); }

/* Table Procedure Call to send version to other side. */  130

static int SendVersionInfo(charon t *charon) { return(SendVersion(charon−>mode, charon−>version)); }

/* Effects: Sends the version across to the other side */ static int

SendVersion(int mode, int version)  140

{ u char data[4]; int retval;

int2octet(version, data);

if (mode == CLIENT MODE)

retval = crn send data(data, 4, PKT CVERSION); else

retval = crn send data(data, 4, PKT SVERSION);  150

return(retval); }

/* Modifies: charon

* Effects: sets version to the minimum of version, and the

* version of the other side, which I have in ”data”.

* If its SERVER MODE, then send my version to the other

* side, since

*  160

* A server−side call means that we need to send the version

* across to the other side. *

* returns an error if the minimum is less that MIN VERSION.

* returns CRN OK on success. */ static int ProcessVersion(charon t *charon) {

int new version;  170

int other version;

octet2int(charon−>data, &other version);

/* The client would have sent it already. But the server needs

* to send this information.

*/ if (charon−>mode == SERVER MODE) SendVersion(SERVER MODE, charon−>version); new version = min(charon−>version, other version);

if (new version < MIN VERSION)

return(CRN BAD VERSION);

charon−>version = new version; return(CRN OK); }

/* Modifies:  charon  190

* Effects: this will take the data input, and unparse it into

* the charon structure, using the unparse routine.

* Different versions may have different structures, so

* if the structure changes, it may be necessary. *

* This should only be called on a client side. *

* Returns CRN OK on success, or an error. */

static int  200

ProcessInfo(charon t *charon) { return(octet2info(charon−>data, charon)); }

/* This procedure sends two packets across, the information

* packet, and then the two KDC packets. The two KDC packets

* are the TGT, and either the rcmd or shared key service tickets *

* This should only be called from a Server side!  210

*

* Requires: the charon structure be complete, and have the

* TGT and either rcmd or shared service key in place

* in the KTEXT spots.

* Effects: Will send the info, tgt, and tkt2 packets across

* to the other side. It will return CRN OK on success,

* or whatever error gets returned from a sub−procedure. */ static int

SendInfoAndKDCPkts(charon t *charon)  220

{ int retval;

/* First, convert my charon structure to something I can send

* across, and send the info string. */

if ((retval = info2octet(charon, &(charon−>data), &(charon−>datalen)))

!= CRN OK) {

free(charon−>data);  230

return(retval); }

if ((retval = crn send data(charon−>data, charon−>datalen, PKT INFO))

!= CRN OK) { free(charon−>data); return(retval); }

free(charon−>data);  240

/* Ok, now we get to send the two KTEXT structures. */ if ((retval = crn rpc put ktext(charon−>tgt, charon−>mode)) != CRN OK) return(retval);

if ((retval = crn rpc put ktext(charon−>tkt2, charon−>mode)) != CRN OK)

return(CRN OK);

}  250

return(retval); /* Effects: Takes the incoming data, which should be the TGT,

* converts it to a KTEXT, then gets another packet,

* which should be another KTEXT packet, which is

* the second ticket, either rcmd.hostinst or the

* shared key. It then will decrypt these with the

* user’s password, and then, if need be, request the

* rcmd ticket. This will then send the auth data

* back to the server. The auth data is the rcmd ticket,

* the encrypted cookie, and the encrypted TGT, in that order. 260

*

* This should only called on the client side. *

* Returns CRN OK on success, or an error. */ static int ProcessServerKDC(charon t *charon) { int retval;

270

if ((retval = DoProcessServerKDC(charon)) != CRN OK)

 crn send error("Could not process server kerberos data");

return(retval); }

static int DoProcessServerKDC(charon t *charon) {

int retval;  280

KTEXT ST auth; CREDENTIALS cred; des cblock key; des key schedule sched; MSG DAT tgt data; MSG DAT cookie data; u char cookie[5]; u char tgt enc data[MAX KTXT LEN]; u char cookie enc data[MAX KTXT LEN];

290

/* First, convert the first packet to ktext */ if ((retval = octet2ktext(charon−>data, charon−>datalen, charon−>tgt))

!= CRN OK) return(retval);

/* Now, get the second ktext packet */ if ((retval = crn rpc get ktext(charon−>tkt2))

!= CRN OK) return(retval);

300

/* Try to decrypt the tickets */ if ((retval = crn cli decrypt tickets(charon)) != CRN OK) return(retval);

/* Now see if we need to get an rcmd ticket. This can be done

* by checking if the princial realm and host realm are the same.

* if they are, then we can send the authentication data, otherwise

* we will have to get the rcmd ticket. */

310

/* Store tkt2 into a file for reference */ if ((retval = crn store ticket(charon, 0, 1)) != CRN OK) return(retval);

if (strcmp(charon−>realm, charon−>hostrealm)) if ((retval = crn get rcmd tkt(charon)) != CRN OK) return(retval);

/* If we’ve gotten here, then we have valid tickets for a TGT and

* an rcmd ticket. So, we need to encrypt everything and ship it  320

* across! */

if ((retval = krb mk req(&auth, RCMD, charon−>hostinst, charon−>hostrealm, 0))

!= KSUCCESS) return(retval);

/* Re−encrypt server KDC packet in session key

* Get credentials from ticket file

*/  330

if ((retval = krb get cred(RCMD, charon−>hostinst, charon−>hostrealm, &cred))

!= KSUCCESS) return(retval);

/* Exctract the session key and make the schedule */ COPY(cred.session, key, sizeof(key)); if ((retval = des key sched(key, sched)) != KSUCCESS) return(retval);

/* Save the session key, if we should */  340

if (charon−>session != NULL) COPY(key, (charon−>session), sizeof(des cblock));

/* Encrypt the TGT in this session key */ tgt data.app data = tgt enc data; if ((tgt data.app length =

krb mk priv(charon−>tgt−>dat, tgt data.app data, charon−>tgt−>length,

sched, key, &charon−>saddr, &charon−>saddr)) == −1) {

return(CRN MK PRIV);

}  350

/* Encrypt the cookie in this session key */ cookie data.app data = cookie enc data; COPY(charon−>cookie, cookie, 4); cookie[4] = charon−>cli encrypt; if ((cookie data.app length =

krb mk priv(cookie, cookie data.app data, 5,

sched, key, &charon−>saddr, &charon−>saddr)) == −1) {

return(CRN MK PRIV);

}  360

if ((retval = crn rpc sendauth(&auth)) != CRN OK) return(retval);

if ((retval = crn rpc senddat(&tgt data)) != CRN OK) return(retval);

if ((retval = crn rpc senddat(&cookie data)) != CRN OK) return(retval);

370

return(CRN OK); }

/* Effects: This is going to be a request for the rcmd ticket.

* Just send it off the the KDC, then take the returned

* packet, and send that back to the other side. *

* This should only be called on the server side. *

* Returns CRN OK on success, or an error. 380

*/ static int ProcessClientKDC(charon t *charon)

{ int retval;

/* First, convert the first packet to ktext */ if ((retval = octet2ktext(charon−>data, charon−>datalen, charon−>tkt2))

!= CRN OK)

return(retval);  390

/* Now send this off to the KDC */ if ((retval = send to kdc(charon−>tkt2, charon−>rcmd, charon−>hostrealm))

!= KSUCCESS) {

 sprintf(errbuf, "%s while trying to send_to_kdc",

error message(ERROR TABLE BASE krb+retval));

crn send error(errbuf); return(retval); }

crn send success();  400

/* XXX −− If there is zero length on this data, then set it to

* MAX KTXT LEN, since the kerberos library may be dain−bramaged

* and not set the length. */ if (!charon−>rcmd−>length) charon−>rcmd−>length = MAX KTXT LEN;

/* Ok, return this off to the other side. */

return(crn rpc put ktext(charon−>rcmd, charon−>mode));  410

}

/* Effects: Expects three packets: a ticket, an encrypted cookie,

* and an encrypted tgt. It will verify that the cookie

* is the same, that the tgt is for the user in question

* (match the username?). If this is the case, then save

* the tgt off to a file, and return CRN OK. Return an

* error, otherwise. *

* This is only called on the server side. 420

*/ static int ProcessCliAuth(charon t *charon) { KTEXT ST auth; AUTH DAT auth dat; MSG DAT msg data; MSG DAT tgt data; MSG DAT cookie data;

u char tgt enc data[MAX KTXT LEN];  430

u char cookie enc data[MAX KTXT LEN]; des cblock key; des key schedule sched; int status;

/* Clear all these structures */ SBCLEAR(auth); SBCLEAR(auth dat); SBCLEAR(tgt data);

SBCLEAR(cookie data);  440

BCLEAR(tgt enc data); BCLEAR(cookie enc data);

tgt data.app data = tgt enc data; cookie data.app data = cookie enc data;

/* These are sent in a group */ if ((status = octet2ktext(charon−>data, charon−>datalen, &auth)) != CRN OK) return(status);

if ((status = crn rpc getdat(&tgt data)) != CRN OK)  450

return(status); if ((status = crn rpc getdat(&cookie data)) != CRN OK) return(status);

/* Read in the request */ if ((status = krb rd req(&auth, RCMD, charon−>hostinst,

0L, &auth dat, KEYFILE)) != KSUCCESS) {

sprintf(errbuf, "%s in krb_rd_req",

error message(ERROR TABLE BASE krb+status));

crn set errbuf(errbuf);  460

return(status); }

/* copy the session key, and create the key schedule */ COPY(auth dat.session, key, sizeof(key)); if ((status = des key sched(key, sched)) != 0) {

sprintf(errbuf, "%s in des_key_sched",

error message(ERROR TABLE BASE krb+status));

crn set errbuf(errbuf);

return(status);  470

}

/* Copy off the session key, if we should */ if (charon−>session != NULL) COPY(key, (charon−>session), sizeof(des cblock));

/* Decrypt the data and store the TGT. */ if ((status =

krb rd priv((u char *)tgt data.app data, tgt data.app length,

sched, key, &charon−>saddr, &charon−>saddr, &msg data))  480

== KSUCCESS) { charon−>tgt−>length = msg data.app length; COPY(msg data.app data, charon−>tgt−>dat, msg data.app length); charon−>tgt−>dat[charon−>tgt−>length] = 0; } else {

sprintf(errbuf, "%s in krb_rd_priv of TGT",

error message(ERROR TABLE BASE krb+status));

crn set errbuf(errbuf); return(status);

}  490

SBCLEAR(msg data); /* Decrypt the cookie and store the charon structure. */ if ((status =

krb rd priv((u char *)cookie data.app data, cookie data.app length,

sched, key, &charon−>saddr, &charon−>saddr, &msg data))

== KSUCCESS) { if (msg data.app length != 5) {

 crn set errbuf("Bad Cookie Length.");

return(CRN ERR COOKIE);  500

}

/* Compare the cookie to make sure they are the same! */ if (CMP(msg data.app data, charon−>cookie, 4)) {

 crn set errbuf("Cookie Mismatch.");

return(CRN ERR COOKIE); } /* Set the value of the encryption flag */ charon−>cli encrypt = msg data.app data[4];

510

} else {

sprintf(errbuf, "%s in krb_rd_priv of cookie",

error message(ERROR TABLE BASE krb+status));

crn set errbuf(errbuf); return(status); }

return(CRN OK); }

520

39

A.2 crn err.c

/* crn err.c −− this file deals with charon errors. Bascially, it

* is just user nicety for returning an error message string from

* an error value. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/crn err.c,v $

* $Author: warlord $ *

*/  10

#if !defined(lint) && !defined(SABER)

static char rcsid crn err c[] = "$Id:  crn_err.c,v 1.2 93/03/29 01:00:52 warlord Exp $";

#endif

#include <charon err.h>

/* Effects: Returns a string that explains the error we received */

char *  20

crn err msg(int error) { char *s;

switch(error) {

case CRN OK:

s = "No Error";

break;

30

case CRN UNKNOWN PKT TYPE:

s = "Unknown packet type";

break;

case CRN ERR DATALEN:

s = "Bad Data Length";

break;

case CRN BAD VERSION:

s = "Bad Version Number";  40

break;

case CRN ALLOC:

s = "Error allocating memory";

break;

case CRN BAD PACKET:

s = "Bad Packet Type";

break;

50

case CRN BADPW:

s = "Bad Password";

break;

case CRN KRB PROT:

s = "Kerberos Protocol Error";

break;

case CRN MK PRIV:

s = "Error in krb_mk_priv";  60

break;

case CRN ERR COOKIE:

s = "Cookie Mismatch";

break; case CRN ERROR:

 s = "Generic Charon Error";

break;

70

default:

 s = "Unknown Error";

break; }

return(s); }

A.3 ia5.c

/* ia5.c −− This code will do IA−5 encoding and decoding of strings

* and characters. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/ia5.c,v $

* $Author: warlord $ * */

10

#if !defined(lint) && !defined(SABER)

static char rcsid ia5 c[] = "$Id:  ia5.c,v 1.3 93/04/01 20:07:17 warlord Exp $";

#endif

#include <stdio.h> #include <sys/types.h> #include <charon prot.h> #include <charon err.h>

#define NUM CHARS 256  /* There are 256 characters in the character−set */  20

#define NUM IA5 CHARS 64 /* 64 characters in IA5 alphabet */

/* IA5 encoding (from RFC 1113): ** Value Encoding Value Encoding Value Encoding Value Encoding

**  0 A  17 R  34 i  51 z
**  1 B  18 S  35 j  52 0
**  2 C  19 T  36 k  53 1
**  3 D  20 U  37 l  54 2
**  4 E  21 V  38 m  55 3
**  5 F  22 W  39 n  56 4  30
**  6 G  23 X  40 o  57 5
**  7 H  24 Y  41 p  58 6
**  8 I  25 Z  42 q  59 7
**  9 J  26 a  43 r  60 8
**  10 K  27 b  44 s  61 9
**  11 L  28 c  45 t  62 +
**  12 M  29 d  46 u  63 /
**  13 N  30 e  47 v
**  14 O  31 f  48 w  (pad) =
**  15 P  32 g  49 x  40
**  16 Q  33 h  50 y

*/

static char EncodeTable[] = {

 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',

 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',  50

 '8', '9', '+', '/'

};

static u char DecodeTable[NUM CHARS]; static int TableInited = 0;

#define ENCODE HEX(a) (EncodeTable[a]) /* Convert 6−bit number to character */ #define DECODE HEX(a) (DecodeTable[a]) /* Convert character to 6−bit number */ #define PAD CHAR '=' /* Padding character */

60

/* Get the six highest bits from the first byte, and shift it to make

* a six−bit number. This is from the octet string c at offset o */ #define CHAR ONE(c, o) ((c[o] & 0xFC) >> 2)

/* Get the low two bits, move them over to the top two bits of a six−bit

* number, and return that */ #define CHAR TWO OF TWO(c, o) ((c[o] & 0x03) << 4)

70

/* Get the low two bits of the first byte and the high 4 bits of the second

* byte and make that into a single six−bit number. */ #define CHAR TWO(c, o) (CHAR TWO OF TWO(c, o) | ((c[o+1] & 0xF0) >> 4))

/* Get the low 4 bits of the second byte and make it the top four bits

* of a six−bit number. */ #define CHAR THREE OF THREE(c, o) ((c[o+1] & 0x0F) << 2)

80

/* Get the 4 low bits of the second byte and the two high bits of the

* third byte, and combine them into a single six−bit number. */ #define CHAR THREE(c, o) (CHAR THREE OF THREE(c, o) | ((c[o+2] & 0xC0) >> 6))

/* Get the low 6 bits of the third byte */ #define CHAR FOUR(c, o) (c[o+2] & 0x3F)

/* Get the first byte two six−bit numbers, the first, and the top two

* bits of the second.  90

*/ #define BYTE ONE(c, o) ((DECODE HEX(c[o]) << 2) | \

((DECODE HEX(c[o+1]) & 0x30) >> 4))

/* Get the second byte from the second and third six−bit values */ #define BYTE TWO(c, o) (((DECODE HEX(c[o+1]) & 0x0F) << 4) | \

((DECODE HEX(c[o+2]) & 0x3C) >> 2))

/* Get the third byte from the third and fourth six−bit values */

#define BYTE THREE(c, o) (((DECODE HEX(c[o+2]) & 0x03) << 6) | \  100

DECODE HEX(c[o+3]))

void ia5 build decode table();

/* Requires: data be valid 8−bit data of length datalen, encoded have

* the length of encodedlen, which is at least

* (((datalen+2)/3)*4) bytes long

* Modifies: encoded and encodedlen

* Effects: This will take data, of length datalen, and convert it

* from 8−bit to 6−bit using the IA5 encoding scheme 110

* explained in RFC 1113. It will place the IA5 encoded

* data into encoded, and will set encodedlen to the length

* of this string. */ int ia5 encode data(u char *data, u int datalen, char *encoded, u int *encodedlen) { int count; int extra bytes; /* 0, 1, or 2 */

int final length = 0;  /* For encoded length */  120

/* Check to make sure we have enough space, just in case someone doesn’t

* understand what a requires clause is all about. ;−)

*/ if (encodedlen != NULL) if (*encodedlen <= ((datalen + 2)/3)*4) return (CRN ERR DATALEN);

/* We need to parse the data in blocks of 3 bytes, which will  130

* get converted into blocks of 4 characters. The output will

* ALWAYS be in blocks of 4 characters, possibly using the PAD CHAR

* as filler, if neccessary, to make the 4 characters.

*/

/* Iterate, Obliterate, Obfuscate, Alleviate.... */ for (count = 0; count < datalen; count += 3) {

/* Figure out if we really have 3 data bytes, or is this the

* end. If its the end, then do we have to pad the data?  140

*/ if (datalen − count >= 3) extra bytes = 0; else extra bytes = datalen % 3; /* 0, 1, or 2 */

/* We are now going to take care of 24 bits worth

* of data. If we do not HAVE 24 bits worth of

* data, then we will finish off with padding characters

*  150

* There will ALWAYS be at least one data byte if we

* have gotten to this point, so we don’t have to

* check that case. */

/* get the six highest bits of the first byte and shift it

* two places to the right to get the first character. This

* Will always be done no matter what extra bytes has to say.

* we DID get here in the first place, no?

*/  160

encoded[final length++] = ENCODE HEX(CHAR ONE(data, count));

/* now lets worry about the second character. If we

* only have one byte, then we get two characters and

* two pads. If we have two bytes, then we get three

* characters and one pad. If we have 0 extra bytes

* then we have no padding at all. */

170

switch (extra bytes) { /* for second character */ case 1: /* We only had one byte, == 2 chars */ encoded[final length++] = ENCODE HEX(CHAR TWO OF TWO(data, count)); break; case 0: case 2: encoded[final length++] = ENCODE HEX(CHAR TWO(data, count)); break; }

180

/* CHARACTER THREE */

switch (extra bytes) { /* for third character */ case 2: encoded[final length++] = ENCODE HEX(CHAR THREE OF THREE(data, count)); break; case 0: encoded[final length++] = ENCODE HEX(CHAR THREE(data, count)); break;

case 1:  190

/* Just a pad character */ encoded[final length++] = PAD CHAR; break; }

/* CHARACTER FOUR */

switch (extra bytes) { /* for fourth character */ case 0:

encoded[final length++] = ENCODE HEX(CHAR FOUR(data, count));  200

break; case 1: case 2: /* Need a pad character */ encoded[final length++] = PAD CHAR; }

} /* for */

/* Set the encoded length to the length of the string */  210

if (encodedlen != NULL) *encodedlen = final length;

encoded[final length] = '\0'; /* NULL terminate our char string! */ return(CRN OK); }

/* Requires: data be valid IA5 of length datalen, decoded be of at least

* length (((datalen+3)/4)*3) bytes long, which is stores

* in decodedlen. 220

* Modifies: decoded and decodedlen

* Effects: This will data data, and IA5 encoded string, and convert

* it to the 8−bit data string and place that into decoded,

* and set decodedlen to then length of the octet string. */ int ia5 decode data(char *data, u int datalen, u char *decoded, u int *decodedlen) { int count;

int extra bytes = 0; /* 0, 1, or 2 */ 230

int final length = 0; /* This will tell us how many

* chars we will have

* ((octetlen + 2)/3)*4 */

/* Build the table if we need to. */ if (!TableInited) ia5 build decode table();

240

if (datalen%4 != 0) /* Must be a multiple of 4! */ return (CRN ERR DATALEN);

count = (datalen/4)*3; if (data[datalen−1] == PAD CHAR) { count−−; if (data[datalen−2] == PAD CHAR) count−−;

}

250

if (decodedlen != NULL) /* Must have enough space */ if (*decodedlen < count) return (CRN ERR DATALEN);

/* Walk down the string of characters in blocks of four. If the

* first is a NULL, then we’ve gotten to the end. Check for pad

* characters, to make sure this works out. */

/* XXX − If a character used is not a valid character as per RFC 1113,  260

* it will be treated as a zero! */

for (count = 0; count < datalen && data[count] != '\0'; count +=4) {

/* Find out how many pad characters there are in this

* four−character sequece. There can be 0, 1, or 2 */ if (datalen − count > 4)

extra bytes = 0;  270

else if (data[count+3] == PAD CHAR) { extra bytes = 1; if (data[count+2] == PAD CHAR)

extra bytes = 2; }

/* First byte */

decoded[final length++] = BYTE ONE(data, count);

280

/* Second byte */

switch (extra bytes) { case 0: case 1: decoded[final length++] = BYTE TWO(data, count); case 2: break; }

290

/* Third byte */

switch (extra bytes) { case 0: decoded[final length++] = BYTE THREE(data, count); case 1: case 2: break; }

300

} /* for */

/* Set the final length of the octen string */ if (decodedlen != NULL) *decodedlen = final length;

return(CRN OK); }

/* Requires:  True  310

* Modifies: TableInited and DecodeTable

* Effects: Builds the decode table from the encode table */ void ia5 build decode table() { int i;

/* Only init the table once */

if (TableInited)  320

return;

TableInited = 1;

/* Zero the table */ BCLEAR(DecodeTable);

/* Set the values for all the IA5 characters */ for (i = 0; i < NUM IA5 CHARS; i++)

DecodeTable[EncodeTable[i]] = i;  330

return; }

A.4 krb.c

/* krb.c −− This file contains the code to deal with the kerberos

* exchange. It knows about this protocol, and will Do The Right

* Thing with inter−realm, etc. Most to the protocol for Charon

* is in this file *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/krb.c,v $

* $Author: warlord $

*  10

*/

#if !defined(lint) && !defined(SABER)

static char rcsid krb c[] = "$Id:  krb.c,v 1.15 93/05/06 21:45:32 warlord Exp $";

#endif

#include <stdio.h> #include <sys/types.h> #include <sys/ioctl.h>

#include <com err.h>  20

#include <krb.h> #include <des.h>

#include <signal.h> #include <setjmp.h>

#ifdef POSIX #include <termios.h> #else

#include <sgtty.h>  30

#endif

#ifdef SERVER #include <sys/time.h> #include <netdb.h> #endif /* SERVER */

#include <charon prot.h> #include <charon.h> /* This includes charon err.h */

#include <parser.h>  40

#include <rpc.h>

/* Place to put error messages */ static char errbuf[BUFSIZ];

static jmp buf env; static void sig restore(); static void push signals(); static void pop signals();

50

/* This is a handle structure for the state of a file descriptor

* when we need to turn off echoing. No one else needs to know about

* this. */ struct crn tty { int fd; /* File Descriptor for this TTY */ #ifdef POSIX struct termios tty; #else

struct sgttyb tty;  60

#endif };

/* Effects: reverts echo to the state on the handle that is passed

* into this function, and then destroys that handle.

*/ void crn revert echo(void *state) {

struct crn tty *tty = state;  70

if (state == NULL)

return;

/* reset state */ #ifndef POSIX ioctl(tty−>fd, TIOCSETP, &(tty−>tty)); #else (void) tcsetattr(tty−>fd, TCSAFLUSH, &(tty−>tty));

#endif  80

/* destroy the handle */ free(tty); }

/* Effects: turns off echoing on the FD, and returns a handle

* to a structure to reset that echoing on that FD. */ void *

crn kill echo(int fd)  90

{ #ifdef POSIX struct termios crn ttya; #else struct sgttyb crn ttya; #endif struct crn tty *tty = (struct crn tty *)malloc(sizeof(struct crn tty));

/* Allocate a handle, save the state, turn off echo ... */

#ifndef POSIX  100

ioctl(fd, TIOCGETP, &crn ttya); ioctl(fd, TIOCGETP, &(tty−>tty)); crn ttya.sg flags &= ˜ ECHO; ioctl(fd, TIOCSETP, &crn ttya); #else (void) tcgetattr(fd, &crn ttya); (void) tcgetattr(fd, &(tty−>tty)); crn ttya.c lflag &= ˜ ECHO; (void) tcsetattr(fd, TCSAFLUSH, &crn ttya);

#endif  110

/* ... and return the handle */ tty−>fd = fd; return((void *)tty); }

/* This is the key procedure from rkinit. It might need to be fiddled

* with to get it to work. The user can always supply their own

* key proc function, of course.

*  120

* Effects: Will print out key information, and then get the

* user’s password, and convert it to a key, and

* return that to the caller. */ static int crn key proc(char *user, char *instance, char *realm, char *arg,

des cblock key)

{ charon t *charon = (charon t *)arg;

char password[BUFSIZ];  130

int ok = 0;

void *tty state; BCLEAR(password);

/*

* If the username does not match the aname in the ticket,

* we will print that too. Otherwise, we won’t. */

140

printf("Charon initialization (%s)", charon−>hostinst);
printf("\nPassword for %s%s%s@%s:  ", user,

(instance[0]) ? "." : "", instance, realm);

fflush(stdout);

push signals(); if (setjmp(env)) {

ok = −1;  150

goto lose; }

tty state = crn kill echo(0);

if (read(0, password, sizeof(password)) == −1) { perror("read"); ok = −1; goto lose;

}  160

if (password[strlen(password)−1] == '\n')

password[strlen(password)−1] = 0;

/* Generate the key from the password and destroy the password */

des string to key(password, key);

lose:

BCLEAR(password);  170

crn revert echo(tty state);

pop signals(); printf("\n");

return(ok); }

#ifdef SERVER  180

/* Effects: This expects both a TGT and either rcmd or shared

* key to be present, and it will then procede to

* send them across and start the protocol. Really! */ static int crn srv decrypt 2 tkts(char *user, char *instance, char *realm, char *arg,

int (*key proc)(), KTEXT *cipp)

{ int retval;

charon t *info = (charon t *)arg;  190

/* XXX to make saber happy */ user++; instance++; realm++;

info−>tkt2 = *cipp;

if ((retval = crn main loop(info)) != CRN OK) { /* fprintf(stderr, ”Fell out of MainLoop with an error\n”); */

longjmp(env, retval);

}  200

return(retval); }

/* Effects: This expects to have the TGT, and it will then go off

* and get the rcmd (or shared key) service ticket. If

* I do get back to here, then I must have successfully

* received the TGT. */ static int

crn srv decrypt tgt(char *user, char *instance, char *realm, char *arg,  210

int (*key proc)(), KTEXT *cipp)

{ charon t *info = (charon t *)arg; int same realm = !strcmp(info−>realm, info−>hostrealm);

/* XXX to make saber happy */ user++; instance++; realm++;

info−>tgt = *cipp;

220

/* If we are the same realm, then get rcmd.hostname. If not,

* then get krbtgt.realm−of−host. */ krb get in tkt(info−>principal, info−>instance, info−>realm,

(same realm) ? RCMD : KRBTGT,

(same realm) ? info−>hostinst : info−>hostrealm, 1, NULL, crn srv decrypt 2 tkts, arg);

/* If I’ve returned from here, then things are good. */

return(CRN OK);  230

} #endif /* SERVER */

/* This is the server−side main call into the charon library. *

* Requires: Echo−ing not be a problem (meaning it should

* be turned off), and that the process be owned by

* the uid who will own the tickets, and also be able

* to read the srvtab file. This can be done by having

* the process’s real id be that of the ticket owner, and 240

* the effective uid be root. In any event, being able to

* read the srvtab is more important than owning the tickets. *

* Modifies: key, enc ses *

* Effects: Will get kerberos tickets for principal and instance

* in the specified realm, and put them in the specified

* file. If the user doesn’t exist, cant store the file,

* or this machine doesnt have an rcmd ticket, then

* fail with an error to the user. Key is a pointer 250

* to the user−defined space to hold a session key, and the

* session key will be stored there if it is not NULL.

* enc ses is a pointer to a character that will be sent

* to the client, and it will contain the binary AND of

* the server and client encrypt session values upon

* return. A NULL pointer is equivalent to no encryption

* information, and nothing will be placed here.

* The ticket will be stored in the default location.

* Returns CRN OK if everything goes ok, and the ticket is

* valid, and all check pass. 260

*/ int crn srv get tickets(char *principal, char *instance, char *realm,

char *hostrealm, int lifetime, char *ticketfile, int version, char *enc ses, des cblock *key)

{ #ifdef SERVER /* All this code is only valid if this library has the server code

* built into it. Make sure, however, that the call is available

* to the user.  270

*/ charon t info; time t cookie; int retval; struct hostent *hp; char hostname[INST SZ]; KTEXT ST rcmd;

/* init error table */

initialize krb error table();  280

SBCLEAR(info); SBCLEAR(rcmd);

/* Set it, just in case */ if (enc ses != NULL) { info.srv encrypt = *enc ses; } else info.srv encrypt = CRN ENC NONE;

290

info.cli encrypt = CRN ENC NONE;

/* Set the mode right here. */ info.mode = SERVER MODE;

/* Copy in the principal, and hostname... These MUST be

* provided. Also set the username to the value provided and

* copy in the instance, if provided. */

if (!principal || !*principal) {  300

fprintf(stderr, "Bad principal\n"); return(CRN ERROR); } if (gethostname(hostname, INST SZ − 1) != 0) {

 fprintf(stderr, "Cannot get my hostname\n");

return(CRN ERROR); } else { /* Resolve this so we can create a ”fake” socket address. */ if ((hp = gethostbyname(hostname)) == NULL) {

fprintf(stderr, "Cannot resolve hostname:  %s\n", hostname);  310

return(CRN ERROR); }

info.saddr.sin family = hp−>h addrtype; COPY(hp−>h addr, &info.saddr.sin addr, hp−>h length);

/* Also get the kerberos hostname for this host. */ strcpy(info.hostinst, krb get phost(hostname)); }

strcpy(info.principal, principal);  320

if (instance) strcpy(info.instance, instance);

/* Copy in the realm if its here. Otherwise, default to local realm,

* no matter how we get it. */ if (realm && *realm) strcpy(info.realm, realm);

else  330

if (krb get lrealm(info.realm, 1) != KSUCCESS)

strcpy(info.realm, KRB REALM);

/* If the hostrealm isn’t provided, then get the host’s realm! */ if (hostrealm && *hostrealm) strcpy(info.hostrealm, hostrealm); else strcpy(info.hostrealm, krb realmofhost(hostname));

/* Set the ticket file if we should */  340

if (ticketfile && *ticketfile) krb set tkt string(ticketfile);

/* Set the cookie. This should be set as the current time of this

* machine. */ cookie = time(0); int2octet(cookie, info.cookie);

/* Find out if we can use the version passed in, otherwise ignore it  350

* and choose MAX VERSION */ if (version < MIN VERSION || version > MAX VERSION) info.version = MAX VERSION; else info.version = version;

/* Set the session key holder. */ info.session = key;

360

/* Give some space */ info.rcmd = &rcmd;

if (geteuid() != 0) {

 fprintf(stderr, "Server not running as root.\n");

return(CRN ERROR); }

/* Send this out to stdout... */

fprintf(stderr, "Charon Initialization...  Waiting for Client Connection.\n");  370

/*

* We need a setjmp here because krb get in tkt ignores the

* return value of decrypt tkt. Thus if we want any of its

* return values to reach the client, we have to jump out of

* the routine. */

if (setjmp(env) == 0) {

/* Get the tickets */  380

if ((retval = krb get in tkt(info.principal, info.instance, info.realm, KRBTGT, info.realm, lifetime, NULL, crn srv decrypt tgt, (char *)&info))

!= KSUCCESS) { crn send error(error message(retval)); } else crn send success();

} else {

crn send error(errbuf);  390

retval = CRN ERROR; } (void)setuid(0); /* make sure I’m root again, if I was before */

/* Set this value to the bit−and of the two values */ if (enc ses != NULL) *enc ses = (info.srv encrypt & info.cli encrypt);

return(retval);

#else /* !SERVER */  400

/* Its not a server. Always return an error! */ return(CRN ERROR); #endif /* SERVER */ } /* This is the client−side mail call into the charon library. *

* Requires: Echo−ing *not* be a problem (or turned off) 410

*

* Modifies: key, enc ses *

* Effects: Will talk to the server and the the TGT data,

* decrypt it, and then encrypt the ticket in the

* rcmd of the server and send it back. Will inform

* the user if any errors occur.

* key proc is a pointer to a user−defined procedure, expecting

* arguments: (user, instance, realm, arg, key). If it is

* NULL, then use pre−defined function to get 420

* the user’s password and create a DES key.

* key is a pointer to the user−defined space to hold a

* des key, and the session key will be stored there if

* it is not NULL.

* enc ses is a pointer to an encrypt session value. It

* will be sent across to the server, and upon exit will

* contain the binary AND of the server and client bytes.

* If it is a NULL pointer, no encryption is assumed.

* Returns CRN OK if everything goes ok.

*/  430

int crn cli get tickets(int version, int (*key proc)(), char *enc ses,

des cblock *key)

{ KTEXT ST tgt, tkt2, rcmd; int status, status2; charon t info;

SBCLEAR(info);

SBCLEAR(tgt);  440

SBCLEAR(tkt2); SBCLEAR(rcmd);

/* init error table */ initialize krb error table();

/* This is to prevent losing current tickets */ krb set tkt string("/dev/null");

/* Set it, just in case */  450

if (enc ses != NULL) { info.cli encrypt = *enc ses; } else info.cli encrypt = CRN ENC NONE;

info.srv encrypt = CRN ENC NONE;

/* Set the mode. No, not the mood, the MODE */ info.mode = CLIENT MODE;

460

/* Set the key proc */ if (key proc != NULL) info.key proc = key proc; else info.key proc = crn key proc;

/* Set the version, if we can. */ if (version < MIN VERSION || version > MAX VERSION) info.version = MAX VERSION;

else  470

info.version = version;

/* Set the session key space */ info.session = key;

/* Put in data for the tickets. */ info.tgt = &tgt; info.tkt2 = &tkt2; info.rcmd = &rcmd;

480

status = crn main loop(&info); status2 = crn get status();

#ifndef DEBUG /* Destroy the local tickets we got −− they’re not useful anyways */ if (info.obtain ticket) dest tkt();

#endif

/* Set this return value */  490

if (enc ses != NULL) *enc ses = (info.cli encrypt & info.srv encrypt);

if (status == CRN OK) return(status2);

return(status); }

int  500

crn cli decrypt tickets(charon t *charon) { KTEXT cip = charon−>tgt; C Block key; /* Key for decrypting cipher */ Key schedule key s;

/* generate a key */ { register int rc;

rc = (*charon−>key proc)(charon−>principal, charon−>instance,  510

charon−>realm, (char *)charon, key);

if (rc)

return(rc); }

des key sched(key, key s);

/* Decrypt information from KDC */ des pcbc encrypt((C Block *)cip−>dat,(C Block *)cip−>dat,

(long) cip−>length, key s, key, 0);  520

/* Decrypt the second ticket */ cip = charon−>tkt2; des pcbc encrypt((C Block *)cip−>dat,(C Block *)cip−>dat,

(long) cip−>length, key s, key, 0);

/* Get rid of all traces of key */ BCLEAR(key); BCLEAR(key s);

530

return(CRN OK); }

/* Effects: Takes the character c and will encrypt it in the

* session key supplied and return that value. It will

* Take the value of mode and enc ses to figure out

* how to encrypt this character. */ char

crn encrypt char(char c, des cblock session, int mode, char enc ses)  540

{ if ((mode == CLIENT MODE && enc ses == CRN ENC CLI) ||

(mode == SERVER MODE && enc ses == CRN ENC SRV) || enc ses == CRN ENC BOTH) return(c + 1); else return(c);

}

/* Effects:  Takes the character c and will decrypt it using the  550

* session key provided, and return that value. */ char crn decrypt char(char c, des cblock session, int mode, char enc ses) { if ((mode == CLIENT MODE && enc ses == CRN ENC SRV) ||

(mode == SERVER MODE && enc ses == CRN ENC CLI) || enc ses == CRN ENC BOTH) return(c − 1);

else  560

return(c); }

/* Effects: sets the errbuf to msg */ int crn set errbuf(char *msg) { strncpy(errbuf, msg, sizeof(errbuf)); }

570

#ifdef POSIX static void (*old sigfunc[NSIG])(); #else static int (*old sigfunc[NSIG])(); #endif POSIX

static void push signals() {

register i;

for (i = 0; i < NSIG; i++)  580

old sigfunc[i] = signal(i,sig restore); }

static void pop signals() {

register i;

for (i = 0; i < NSIG; i++)

signal(i,old sigfunc[i]); }

590

static void sig restore(sig,code,scp)

int sig,code;

struct sigcontext *scp; {

longjmp(env,1); }

A.5 lib krb.c

/* lib krb.c −− This file contains functions that should be in

* libkrb.a, * or whatever kerberos library is being used, but which

* needs to be called * internally, since I don’t really get the

* functionality of the kerberos * library that I wan’t to have....

* Therefore I have created these functions in order to get the

* functionality I needed. A lot of it is taken out of the kerberos

* library. *

* Created by: Derek Atkins <warlord@MIT.EDU>

*  10

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/lib krb.c,v $

* $Author: warlord $ * */

#if !defined(lint) && !defined(SABER)

static char rcsid lib krb c[] = "$Id:  lib_krb.c,v 1.4 93/04/01 19:59:07 warlord Exp $";

#endif

#include <sys/time.h>  20

#include <krb.h> #include <des.h> #include <charon prot.h> #include <charon err.h> #include <krb prot.h> #include <stdio.h>

extern int krb debug;

/* Requires:  ticket cache to have been initialized already.  30

* Effects: takes a ticket an stores it in the ticket cache file.

* If init cache == 1, then initialize the ticket cache.

* If ticket == 0, then use charon−>tkt2, else use

* ticket−>rcmd. *

* This was taken from krb get in tkt.c */ int crn store ticket(charon t *charon, int ticket, int init cache)

{  40

char *ptr; KTEXT cip; C Block ses; /* Session key for tkt */ char s name[SNAME SZ]; char s instance[INST SZ]; char rlm[REALM SZ]; int lifetime; int kvno; /* Kvno for session key */ KTEXT ST tkt st;

KTEXT tkt = &tkt st; /* Current ticket */ 50

struct timeval t local; int kerror;

if (!ticket)

cip = charon−>tkt2; else cip = charon−>rcmd;

ptr = (char *) cip−>dat;

60

/* extract session key */ COPY(ptr,ses,8); ptr += 8;

if ((strlen(ptr) + (ptr − (char *) cip−>dat)) > cip−>length) return(CRN BADPW);

/* extract server’s name */ (void) strncpy(s name,ptr, sizeof(s name)−1);

s name[sizeof(s name)−1] = '\0';  70

ptr += strlen(s name) + 1;

if ((strlen(ptr) + (ptr − (char *) cip−>dat)) > cip−>length) return(CRN BADPW);

/* extract server’s instance */ (void) strncpy(s instance,ptr, sizeof(s instance)−1); s instance[sizeof(s instance)−1] = '\0'; ptr += strlen(s instance) + 1;

80

if ((strlen(ptr) + (ptr − (char *) cip−>dat)) > cip−>length) return(CRN BADPW);

/* extract server’s realm */ (void) strncpy(rlm,ptr, sizeof(rlm)); rlm[sizeof(rlm)−1] = '\0'; ptr += strlen(rlm) + 1;

/* extract ticket lifetime, server key version, ticket length */

/* be sure to avoid sign extension on lifetime! */  90

lifetime = (unsigned char) ptr[0]; kvno = (unsigned char) ptr[1]; tkt−>length = (unsigned char) ptr[2]; ptr += 3;

if ((tkt−>length < 0) || ((tkt−>length + (ptr − (char *) cip−>dat)) > cip−>length)) return(CRN BADPW);

/* extract ticket itself */  100

COPY(ptr,(tkt−>dat),tkt−>length); ptr += tkt−>length;

/* Pass KDC time stamp −− I’m using the Cookie to verify it. */ ptr += 4;

/* initialize ticket cache, if we should */ if (init cache) { char ticketfile[200];

/* First, set the ticket file name */  110

sprintf(ticketfile, "%s%s.crn", TMP TKT FILE, charon−>principal); krb set tkt string(ticketfile);

if (in tkt(charon−>principal,charon−>instance) != KSUCCESS)

return(INTK ERR);

charon−>obtain ticket = 1; }

120

/* Get local time */ (void) gettimeofday(&t local,(struct timezone *) 0);

/* stash ticket, session key, etc. for future use */ if ((kerror = save credentials(s name, s instance, rlm, ses,

lifetime, kvno, tkt, t local.tv sec))

!= KSUCCESS) return(kerror);

return(CRN OK);  130

}

/* This is in the kerberos library */ extern int swap bytes;

/* Effects: This will get an rcmd ticket from the kerberos server

* by going across the rpc connection and asking for it. It will

* then store that ticket in a file.

* returns CRN OK on success, or an error.

*/  140

int crn get rcmd tkt(charon t *charon) { KTEXT ST pkt st; KTEXT pkt = & pkt st; /* Packet to KDC */ KTEXT ST rpkt st; KTEXT rpkt = &rpkt st; /* Returned packet */ KTEXT cip = charon−>rcmd; /* Returned Ciphertext */ CREDENTIALS cr;

Key schedule key s;  150

long time ws = 0; int msg byte order; int kerror; unsigned long rep err code; static struct timeval tt local = { 0, 0 };

BCLEAR(pkt); BCLEAR(rpkt);

/* Reset this variable */  160

swap bytes = 0;

/* Create skeleton of packet to be sent */ (void) gettimeofday(&tt local,(struct timezone *) 0);

pkt−>length = 0;

/*

* Look for the session key (and other stuff we don’t need)

* in the ticket file for krbtgt.realm@lrealm where ”realm”  170

* is the service’s realm (passed in ”realm” argument) and

* lrealm is the realm of our initial ticket. If we don’t

* have this, we will try to get it. */

if ((kerror = krb get cred(KRBTGT,charon−>hostrealm,charon−>realm,&cr))

!= KSUCCESS) {

return(kerror); }

180

/*

* Make up a request packet to the ”krbtgt.realm@lrealm”.

* Start by calling krb mk req() which puts ticket+authenticator

* into ”pkt”. Then tack other stuff on the end. */

kerror = krb mk req(pkt,KRBTGT,charon−>hostrealm,charon−>realm,0L);

if (kerror)

return(AD NOTGT);  190

/* timestamp */ COPY(&time ws,(pkt−>dat+pkt−>length),4); pkt−>length += 4; *(pkt−>dat+(pkt−>length)++) = (char) 1; (void) strcpy((char *) (pkt−>dat+pkt−>length),RCMD); pkt−>length += 1 + strlen(RCMD); (void) strcpy((char *)(pkt−>dat+pkt−>length),charon−>hostinst); pkt−>length += 1 + strlen(charon−>hostinst);

200

rpkt−>length = 0; /* Send the request to the local ticket−granting server

* THIS IS SENT VIA THE RPC PORT *

* Also, I *know* that this is only going to be a client! */ crn rpc put ktext(pkt, CLIENT MODE); if ((kerror = crn get status()) != CRN OK)

return(kerror);  210

crn rpc get ktext(rpkt);

/* check packet version of the returned packet */ if (pkt version(rpkt) != KRB PROT VERSION ) return(CRN KRB PROT);

/* Check byte order */ msg byte order = pkt msg type(rpkt) & 1; swap bytes = 0;

if (msg byte order != HOST BYTE ORDER)  220

swap bytes++;

switch (pkt msg type(rpkt) & ˜ 1) { case AUTH MSG KDC REPLY: break; case AUTH MSG ERR REPLY: COPY(pkt err code(rpkt), &rep err code, 4); if (swap bytes) swap u long(rep err code);

return(rep err code);  230

default: return(CRN KRB PROT); }

/* Extract the ciphertext */ cip−>length = pkt clen(rpkt); /* let clen do the swap */

COPY(pkt cipher(rpkt),(cip−>dat),cip−>length);

240

#ifndef NOENCRYPTION /* Attempt to decrypt it */

key sched(cr.session,key s);

if (krb debug) printf("About to do decryption ...");

pcbc encrypt((C Block *)cip−>dat,(C Block *)cip−>dat,

(long) cip−>length,key s,cr.session,0);

#endif /* !NOENCRYPTION */ /* Get rid of all traces of key */

BCLEAR(cr.session);  250

BCLEAR(key s);

/* Continue to parse this ticekt and then store it */ return(crn store ticket(charon, 1, 0)); }

A.6 parser.c

/* parser.c −− This will have the parse and unparse routines to convert

* internal structures into something that can be sent over the

* wire and retreived. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/parser.c,v $

* $Author: warlord $ *

*/  10

#if !defined(lint) && !defined(SABER)

static char rcsid parser c[] = "$Id:  parser.c,v 1.4 93/05/05 11:55:10 warlord Exp $";

#endif

#include <stdio.h> #include <sys/types.h> #include <krb.h> #include <charon prot.h>

#include <charon err.h>  20

/* Requires: octet be of at least size 4 bytes, and int >= 0, and int

* be of at most size 4 bytes

* Modifies: octet

* Effects: converts the integer num to an MSB−first represented

* octet string, and put this into octet. */ void int2octet(int num, u char *octet)

{  30

int i;

for (i = 3; i >=0; i−−) {

octet[i] = num%256;

num /= 256; } }

/* Requires: octet be of size 4 bytes.

* Modifies: num 40

* Effects: converts an MSB−first octet string to an integer. */ void octet2int(u char *octet, int *num) { int i;

for (*num = 0, i = 0; i < 4; i++) { *num *= 256;

*num += octet[i];  50

} }

/* This is a description of the format of the octet string from

* the info structure, for each version. If there is a new protocol

* version, then this code might need to be changed. *

* For version 2: *

* 0...3 | 4...8 | 9 | 10 ... end  60

* cookie |IP addr| e | principal 0 instance 0 realm 0 host 0 hostrealm 0 */

/* Modifies: data, datalen

* Effects: will convert the charon structure to an octet string * of a form capable of being sent across the wire.

* The format should match that of octet2info.

* Will malloc the space needed.

* Returns CRN OK on success, error otherwise.

*/  70

int info2octet(charon t *charon, u char **data, u int *datalen) { *datalen = 0; /* initialize length to zero */

switch (charon−>version) {

case 2: /* allocate max size == 4 + sum(sizes of principal, inst, realm, hostinst,

* and hostrealm).  80

*/ *data = (u char *)malloc(4 + sizeof(struct sockaddr in) +

sizeof(charon−>principal) + sizeof(charon−>instance) + sizeof(charon−>realm) + sizeof(charon−>hostinst) + sizeof(charon−>hostrealm));

if (*data == NULL)

return(CRN ALLOC);  90

/* Ok, now that its actually allocated, lets convert it all.

* First, convert the cookie */ COPY(charon−>cookie, *data, 4); *datalen += 4;

/* Copy in the address */ COPY(&(charon−>saddr.sin addr), *data+*datalen,

sizeof(charon−>saddr.sin addr));  100

*datalen += sizeof(charon−>saddr.sin addr);

/* Put in the srv encrypt byte */ *(*data+*datalen) = charon−>srv encrypt; *datalen++;

/* Now, copy each of the other parts. Make sure to keep the

* NULL separation. Start with the principal. */

strcpy(*data+*datalen, charon−>principal);  110

*datalen += strlen(charon−>principal) + 1;

/* Copy the instance */ strcpy(*data+*datalen, charon−>instance); *datalen += strlen(charon−>instance) + 1;

/* And the realm */ strcpy(*data+*datalen, charon−>realm); *datalen += strlen(charon−>realm) + 1;

120

/* Now the host instance */ strcpy(*data+*datalen, charon−>hostinst); *datalen += strlen(charon−>hostinst) + 1;

/* And finally the host realm */ strcpy(*data+*datalen, charon−>hostrealm); *datalen += strlen(charon−>hostrealm) + 1;

break;

130

default: return(CRN BAD VERSION); } /* Requires: data be in the correct format for the version.

* Modifies: charon

* Effects: This function will convert an octet string to an internal 140

* structure. It is the reverse of info2octet, and it should

* match at all times. The version of the software can be used

* as a flag if the structure changes.

* Returns CRN OK on success, error otherwise. */ int octet2info(u char *data, charon t *charon) { char *char p;

150

switch(charon−>version) {

case 2: /* Pull out the cookie */ COPY(data, charon−>cookie, 4); data += 4;

/* Now get the server address */ COPY(data, &(charon−>saddr.sin addr), sizeof(charon−>saddr.sin addr));

data += sizeof(charon−>saddr.sin addr);  160

/* Get the server’s encryption byte */ charon−>srv encrypt = *data; data++;

/* Now get the principal */ char p = charon−>principal; do { *(char p++) = *data;

} while (*(data++) != NULL);  170

/* And the instance */ char p = charon−>instance; do { *(char p++) = *data; } while (*(data++) != NULL);

/* And the realm */ char p = charon−>realm;

do {  180

*(char p++) = *data; } while (*(data++) != NULL);

/* And the hostname */ char p = charon−>hostinst; do { *(char p++) = *data; } while (*(data++) != NULL);

/* And the hostrealm */  190

char p = charon−>hostrealm; do { *(char p++) = *data; } while (*(data++) != NULL);

break;

default: return (CRN BAD VERSION);

}  200

/* Effects: Takes an octet string and puts it into a ktext

* structure. */ int octet2ktext(u char *octet, int datalen, KTEXT pkt)

{  210

COPY(octet, pkt−>dat, datalen); pkt−>length = datalen; return(CRN OK); }

A.7 rpc.c

/* Rpc.c −− This file contains the code to do the rpc over the serial

* line. It will do actual to the transmissions over the port. It

* keeps an internal set of file descriptors for input and output (they

* do not have to be the same). *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/rpc.c,v $

* $Author: warlord $

*  10

*/

#if !defined(lint) && !defined(SABER)

static char rcsid rpc c[] = "$Id:  rpc.c,v 1.6 93/04/01 19:59:23 warlord Exp $";

#endif

#include <stdio.h> #include <ia5.h> #include <parser.h>

#include <charon prot.h>  20

#include <charon err.h>

#if defined(vax) || defined(ibm032) char *fgets(), *fputs(); #endif

int charon debug = 0;

/* default to stdin and stdout */

static FILE *InputFile = stdin, *OutputFile = stdout, *ErrorFile = stderr;  30

static int (*Putc)() = NULL, (*Getc)() = NULL; static int (*Puts)() = NULL; static int (*FPuts)() = NULL, (*FGets)() = NULL;

/* Requires: file be a valid file, open for reading

* Modifies: InputFile

* Effects: Sets the input file to “file”. */ void

crn set input file(FILE *file)  40

{ InputFile = file; return; }

/* Requires: file be a valid file, open for writing

* Modifies: OutputFile

* Effects: Sets the output file to “file”. */

void  50

crn set output file(FILE *file) { OutputFile = file; return; }

/* Requires: fcn be a function that puts a single character to a

* file, of the form int fcn(char c)

* Modifies: Putc

* Effects: Sets the Putc function. 60

*/ void crn set putc fcn(int (*fcn)()) { Putc = fcn; }

/* Requires: fcn be a function that gets a single character from a

* file, of the form int fcn()

* Modifies: Getc 70

* Effects: Sets the Getc function. */ void crn set getc fcn(int (*fcn)()) { Getc = fcn; }

/* Requires: fcn be of form int fcn(char *s, int len)

* Effects: Sets the Puts function. This is for kermit compatibility. 80

*/ void crn set puts fcn(int (*fcn)()) { Puts = fcn; }

/* Requires: fcn be a function that puts a line of n chars to the

* file, and includes a CR/LF at the end. fc is of the form

* char *fcn(char *line, FILE *stream) 90

* Modifies: FPuts

* Effects: Sets the FPuts function. */ void crn set fputs fcn(int (*fcn)()) { FPuts = fcn; }

/* Requires:  fcn be a function that gets a line from a file, ignoring  100

* training CR/LF’s, of the form

* char *fcn(char *data, int space, FILE *stream)

* Modifies: FGets

* Effects: Sets the FGets function. */ void crn set fgets fcn(int (*fcn)()) { FGets = fcn;

}  110

/* Effects: Will take the KTEXT packet and send it across to the

* other side. It will vary the type depending on whether

* the srv is SERVER MODE or CLIENT MODE.

* Retuns CRN OK on success, otherwise an error. */ int crn rpc put ktext(KTEXT pkt, int srv) {

return (crn send data(pkt−>dat, pkt−>length,  120

(srv == CLIENT MODE) ? PKT CKDC : PKT SKDC));

}

/* Modifies: pkt

* Effects: Will get a KTEXT packet from the other side. It will

* set equal to a boolean of whether the types matched or

* not.

* Retuns CRN OK on success, otherwise an error. */

int  130

crn rpc get ktext(KTEXT pkt) { u char type; pkt−>length = MAX KTXT LEN; /* set to max size for default */

return(crn read data(pkt−>dat, (u int *)&(pkt−>length), &type)); }

/* Effects:  Sends an AUTH DAT packet to the other side.  140

*/ int crn rpc sendauth(KTEXT auth) { return(crn send data(auth−>dat, auth−>length, PKT CAUTH)); }

/* Effects: Send a MSG DAT packet to the other side. */

int  150

crn rpc senddat(MSG DAT *data) { return(crn send data(data−>app data, data−>app length, PKT CAUTH)); }

/* Requires: Space to have been provided already.

* Effects: Reads in a MSG DAT structure from the other side and puts

* it in the space provided. */

int  160

crn rpc getdat(MSG DAT *data) { int status; u char type;

data−>app length = MAX KTXT LEN; /* initialize to largest size. */

if ((status = crn read data(data−>app data, &data−>app length, &type)) != CRN OK)

return(status);  170

if (type != PKT CAUTH)

return(CRN BAD PACKET);

return(CRN OK); }

/* Effects: Reads a packet and expects it to be a status packet, and

* will return CRN OK on good status, or an error.

*/  180

int crn get status() { char msg[BUFSIZ]; u char type; u int len = sizeof(msg); int status;

if ((status = crn read data((u char *)msg, &len, &type)) != CRN OK)

return(status);  190

if (type != PKT STATUS)

return(CRN BAD PACKET);

return(crn read status(msg, len)); }

/* Effects: Parse a data and length, and return CRN OK on error, or

* CRN ERROR and print an error message.

*/  200

int crn read status(char *msg, u int len) { if (len == 0) return(CRN OK);

fprintf(stderr, "%s\n", msg); return(CRN ERROR); }

210

/* Effects: Send an OK status to the other side. */ void crn send success() { crn send data(NULL, 0, PKT STATUS); }

/* Effects: Send an error message across to the other side.

*/  220

void crn send error(char *msg) { crn send data((u char *)msg, strlen(msg)+1, PKT STATUS); }

/* Requires: crn read line blocks.

* Modified: data, datalen

* Effects: This will wait for an incoming packet, somehow, and then

* it will read in the packettype, packet length, and the 230

* packet itself. It will allocate enough data for the packet

* and return the packet type to the caller. */ int crn wait for data(u char **data, u int *datalen, u char *datatype) { u char type byte; int llen; char type[5];

char enc length[9];  240

u char length[4]; int retval; u int count, encodedlen;

/* First, read the packet type byte (should only be one character) */ if ((retval = crn read line(type, sizeof(type))) != 1) return(CRN BAD PACKET);

/* This is special, since I really do know the IA5 encoding scheme here */

type[1] = type[2] = type[3] = '=';  250

type[4] = '\0';

/* Decode the type−byte, and get the llen */ encodedlen = 5; if ((retval = ia5 decode data(type, 4, &type byte, &encodedlen)) != CRN OK) return(retval); *datatype = PKT TYPE(type byte); llen = PKT LLENGTH(type byte);

/* Read in the length field */  260

count = crn read line(enc length, sizeof(enc length));

/* And convert it back ... */ BCLEAR(length); encodedlen = 4; if ((retval = ia5 decode data(enc length, count, length + (4 − llen),

&encodedlen)) != CRN OK) {

return(retval); } /* ... to an integer */ octet2int(length, (int *)datalen);

if (charon debug) fprintf(ErrorFile, "crn_len: %d\n", *datalen);

/* Now allocate and clear the space for this */ *data = (u char *)malloc(*datalen + 1); ZERO(*data, *datalen);

280

return(crn read packet data(*data, *datalen)); }

/* Requires: Valid Input and Output Files, valid 8−bit data and datalength,

* and a valid type

* Modifies: Output stream

* Effects: Will convert the data to a 6−bit, IA5 encoding, and send

* that to the output file.

* Returns 0 on success, or an error code, otherwise

*/  290

int crn send data(u char *data, u int datalen, u char datatype) { u char type byte; int llen, retval; char type[5]; char enc length[9]; u char length[4]; char *coded data;

u int count, encodedlen;  300

/* Encoded the length into an octet */ int2octet(datalen, length);

llen = 4; if (datalen < 0x010000) llen = 2; if (datalen < 0x100) llen = 1;

310

type byte = TYPE BYTE(llen, datatype);

/* Encode and send the type.. Only the top byte is significant. */ encodedlen = 5; if ((retval = ia5 encode data(&type byte, 1, type, &encodedlen)) != CRN OK) return(retval); crn put line(type, 1);

/* Encoded and send the length */

encodedlen = 9;  320

if ((retval = ia5 encode data(length + (4 − llen), llen, enc length,

&encodedlen)) != CRN OK)

return(retval); crn put line(enc length, encodedlen);

if (charon debug) fprintf(ErrorFile, "crn_len: %d\n", datalen);

/* Encode the data */

encodedlen = IA5 LENGTH(datalen);  330

coded data = (char *)malloc(encodedlen); if ((retval = ia5 encode data(data, datalen, coded data, &encodedlen))

!= CRN OK) return(retval);

if (charon debug) fprintf(ErrorFile, "num_chars: %d\n", encodedlen); for (count = 0; count <= encodedlen; count += LINE SIZE) {

if (encodedlen − count < LINE SIZE)  340

crn put line(coded data+count, encodedlen % LINE SIZE); else crn put line(coded data+count, LINE SIZE);

}

free(coded data); return(CRN OK); }

/* Requires:  data be pointing into valid data, long enough to hold all the  350

* data coming in the packet, datalen should start out with the

* amount of space allocated, and must be a valid pointer, as

* datatype must also be a valid non−null pointer.

* Modifies: *data, *datalen, and *datatype

* Effects: reads data in IA5 encoding, converts it to 8−bit binary,

* and put it into the space of data, put the length of the

* data into datalen, and the packet type into datatype.

* Returns 0 on success, error code otherwise. */

int  360

crn read data(u char *data, u int *datalen, u char *datatype) { u char type byte; int llen, retval; char type[5]; char enc length[9]; u char length[4]; u int count, encodedlen;

/* First, read the packet type byte (should only be one character) */  370

if ((crn read line(type, sizeof(type))) != 1) return(CRN BAD PACKET);

/* This is special, since I really do know the IA5 encoding scheme here */ type[1] = type[2] = type[3] = '='; type[4] = '\0';

/* Decode the type−byte, and get the llen */ encodedlen = 4;

type byte = 0;  380

if ((retval = ia5 decode data(type, 4, &type byte, &encodedlen)) != CRN OK) return(retval); *datatype = PKT TYPE(type byte); llen = PKT LLENGTH(type byte);

/* Read in the length field */ count = crn read line(enc length, sizeof(enc length));

/* And convert it back ... */

BCLEAR(length);  390

encodedlen = 4; if ((retval = ia5 decode data(enc length, count, length + (4 − llen),

&encodedlen)) != CRN OK)

return(retval);

/* ... to an integer */ octet2int(length, (int *)datalen);

return(crn read packet data(data, *datalen));

}  400

/* Requires: space must be allocated already for data. Datalen must be

* the length of the data.

* Modified: data

* Effects: Reads in the data in IA5 and decodes it to octet into * data. */ static int crn read packet data(u char *data, u int datalen)

{  410

char *coded data; u int len, count = 0, enc len, encodedlen;

if (datalen == 0) { *data = '\0'; return(CRN OK); }

enc len = IA5 LENGTH(datalen);

coded data = (char *)malloc(enc len−−);  420

while (count < enc len) { len = crn read line(coded data + count, enc len − count); count += len; }

encodedlen = datalen; return(ia5 decode data(coded data, enc len, data, &encodedlen)); }

430

/* Requires: Enough data is allocated.

* Effects: Read a line into data, without including CR or LF. Returns

* number of characters read. Ignore empty lines and try again.

* Strip off ending CR, LF, and spaces. */ static int crn read line(char *data, int space) { char buf[LINE SIZE+3];

char c;  440

int n = 0, i = 0;

/* No reason to read in zero data */ if (space == 0) return(0);

BCLEAR(buf);

/* Read in the line */

if (FGets != NULL) {  450

FGets(buf, LINE SIZE+3, InputFile);

} else { if (Getc == NULL) { FGets = (int (*)())fgets; return(crn read line(data, space)); }

do {

c = Getc();  460

if (c != '\r' && c != '\n' && c != '\0') {

buf[n++] = c; } } while (c != '\n' && n < LINE SIZE+3); }

/* Strip off trailng stuff */ for (n = strlen(buf); n >= 0; n−−) if (buf[n] == '\r' || buf[n] == '\n' || buf[n] == ' ')

buf[n] = '\0';  470

/* Ignore blank lines */ if ((n = strlen(buf)) == 0) return(crn read line(data, space));

if (charon debug) fprintf(ErrorFile, "crn_get: %s\n", buf);

/* Copy in the known amount of space into the data provided */

strncpy(data, buf, space);  480

return(n); }

/* Requires: len be less than LINE SIZE

* Effects: write len characters of data out, and add a CR and LF */ static int crn put line(char *data, u int len) {

char c[LINE SIZE+3];  490

int n = 0;

if (len == 0 || strlen(data) == 0) return(CRN OK);

BCLEAR(c); strncpy(c, data, len); strcat(c, "\r\n");

if (charon debug)  500

fprintf(ErrorFile, "crn_put: %s", c);

if (FPuts != NULL) { FPuts(c, OutputFile); return(CRN OK); }

if (Puts != NULL) { Puts(c, len + 2);

return(CRN OK);  510

}

if (Putc == NULL) { FPuts = (int(*)())fputs; return(crn put line(data, len)); }

for (n = 0; n < len + 2; n++) { Putc(c[n]);

}  520

return(CRN OK); }

/* Modifies: ErrorFile, charon debug

* Effects: Tries to open file for write and will set ErrorFile to

* that file if possible. Also sets charon debug if successful. */ void

crn rpc set debug file(char *file)  530

{ ErrorFile = fopen(file, "w"); if (ErrorFile != NULL) charon debug = 1;

}

72

Appendix B

Charon Library Include Files

B.1 charon.h

/* charon.h −− This file contains prototypes and definitions for

* external programs for charon, the serial−line kerberos library

* and extentions. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/include/RCS/charon.h,v $

* $Author: warlord $ *

*/  10

#ifndef CHARON H #define CHARON H

#include <stdio.h> #include <charon err.h> #include <des.h>

/* Sets the input file to this file */

extern void crn set input file(FILE *file);  20

/* Sets the output file to this file */ extern void crn set output file(FILE *file);

/* Sets the output character function to this function. It expects

* the form: int fcn(char c) */ extern void crn set putc fcn(int (*fcn)());

/* Sets the input character function, of the form:  30

* int fcn() */ extern void crn set getc fcn(int (*fcn)());

/* Set an output−line function of the form:

* int fcn(char *line, int len) */ extern void crn set puts fcn(int (*fcn)());

/* Sets the output−line function, of the form:  40

* int fcn(char *line, FILE *stream) */ extern void crn set fputs fcn(int (*fcn)()); /* Sets the input−line funtion, of the form:

* int fcn(char *data, int space, FILE *stream) */ extern void crn set fgets fcn(int (*fcn)());

/* Server call to get tickets. Returns CRN OK on success, or  50

* some error code. */ extern int crn srv get tickets(char *principal, char *instance,

char *realm, char *hostrealm, int lifetime, char *ticketfile, int version, char *enc ses, des cblock *key);

/* Client call to get tickets. Returns CRN OK on success, or

* some error code.

*/  60

extern int crn cli get tickets(int version, int (*key proc)(), char *enc ses, des cblock *key);

/* Return an error message from an error number */ extern char *crn err msg(int error);

/* Set an error file for debugging information to go to. */ extern void crn rpc set debug file(char *file);

/* Turn echo of on the file descriptor */  70

extern void *crn kill echo(int fd);

/* Revert echoing back to its predescesor state */ extern void crn revert echo(void *state);

/* These definitions are for encryption using Charon. They define how

* the session is going to be encrypted. There are four possibilities

* for this value. */

#define CRN ENC NONE 0 /* No encryption */ 80

#define CRN ENC CLI 1 /* Encrypt from the Client only */ #define CRN ENC SRV 2 /* Encrypt from the Server only */ #define CRN ENC BOTH 3 /* Encrypt both directions */

#endif /* CHARON H */

B.2 charon err.h

/* charon err.h −− this file contains error codes for Charon. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/include/RCS/charon err.h,v $

* $Author: warlord $ * */

#ifndef CRN ERR H  10

#define CRN ERR H

#define CRN OK 0 #define CRN UNKNOWN PKT TYPE −1 #define CRN ERR DATALEN −2 #define CRN BAD VERSION −3 #define CRN ALLOC −4 #define CRN BAD PACKET −5 #define CRN BADPW −6

#define CRN KRB PROT −7 20

#define CRN MK PRIV −8 #define CRN ERR COOKIE −9 #define CRN ERROR −10

#endif /* CRN ERR H */

B.3 charon prot.h

/* charon prot.h −− This file contains protocol definitions for charon,

* the kerberos extentions for secure serial operations. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/include/RCS/charon prot.h,v $

* $Author: warlord $ * */

10

#ifndef CHARON PROT H #define CHARON PROT H

#include <sys/types.h> #include <netinet/in.h> #include <krb.h>

/* A type byte is 8 bits of data, of the form:

* llttttuu

*  20

* where:

* ll is an encoding of the length of the length.

* ll == 0 : Llength = 1

*  == 1 :  = 2
*  == 2 :  = 4
*  == 3 :  = the next byte

*

* tttt is the packet type. *

* uu is unused 30

*/

/* This will return the value of the ”ll” field in the packet type

* byte. */ #define PKT LLEN(a) ((a & 0xC0) >> 6)

/* This will return the Length of the length (Llength).

* a return of zero means to read the next byte

*/  40

#define PKT LLENGTH(a) ((PKT LLEN(a) == 0) ? 1 : \ (PKT LLEN(a) == 1) ? 2 : \ (PKT LLEN(a) == 2) ? 4 : 0)

/* This will take a type byte and return a packet type */ #define PKT TYPE(a) ((a&0x3C) >> 2)

/* This will take a packet type and a Length−of−length and create

* a type byte. If llen != 1, 2, or 4, then there might be a problem!

*/  50

#define TYPE BYTE(llen, pkt) ((((llen == 1) ? 0 : \ (llen == 2) ? 1 : \

(llen == 4) ? 2 : 3) << 6) | (pkt << 2))

/* This is the length of an IA5 line */ #define LINE SIZE 64

/* Define the different packet types, here. Do not change any of

* this without also changing the table in ”table.h” in the library.

* It depends upon these number for the dispatch tables to work.  60

*/ #define PKT STATUS 0 /* status information */ #define PKT CVERSION 1 /* client version */ #define PKT SVERSION 2 /* server version */

#define PKT INFO  3  /* information packet */

#define PKT SKDC  4  /* server kerberos data */
#define PKT CKDC  5  /* client kerberos data (for interrealm) */

#define PKT CAUTH 6 /* client authentication data */

#define MAX NUM PKTS 16  /* There can only be 16 packet types */  70

/* This is a definition to know if I am the server or the client.

* It is not set by anything, but you know which by whether the

* application calls the client or server function. Do not change

* this, as the dispatch tables depend upon this. */ #define CLIENT MODE 0 /* I am a client appliation */ #define SERVER MODE 1 /* I am a server application */

/* This defines how high our internal versions can go. Basically, it  80

* is the number of protocols we have defined. It is not the same

* as the client/server version numbers passed across in the handshake.

* Currently, that isn’t being used, but I’m allowing it for future

* expansion of protocols. */ #define MAX VERSION 2 /* Max number of versions defined in table.h */ #define MIN VERSION 2 /* Minimum version number supported */

/* These are useful definitions for things... */

#if defined(sun) && defined(sparc) && defined(POSIX)  90

/* solaris likes these... */

#define BCLEAR(a) memset((char *)(a), 0, sizeof(a)) #define SBCLEAR(a) memset((char *)&(a), 0, sizeof(a)) #define ZERO(a,l) memset((char *)(a), 0, (l)) #define COPY(s,d,l) memcpy((char *)(d), (char *)(s), (l)) #define CMP(a,b,l) memcmp((char *)(a), (char *)(b), (l)) #else /* These won’t always work! (especially on solaris) */ #define BCLEAR(a) bzero((char *)(a), sizeof(a)) #define SBCLEAR(a) bzero((char *)&(a), sizeof(a))

#define ZERO(a,l) bzero((char *)(a), (l))  100

#define COPY(s,d,l) bcopy((char *)(s), (char *)(d), (l)) #define CMP(a,b,l) bcmp((char *)(a), (char *)(b), (l)) #endif #define min(a,b) (((a) < (b)) ? (a) : (b)) #define max(a,b) (((a) > (b)) ? (a) : (b))

/* The tickets to get */ #define RCMD "rcmd" #define KRBTGT "krbtgt"

110

/* This is the temp directory, for where the temp tickets should go */ #define TMP TKT FILE "/tmp/tkt_"

/* This is an internal structure that gets passed around inside

* the library. It contains a lot of information about state and

* other interesting tidbits. */ typedef struct charon t {

int  mode;  /* client or server */
int  version;  /* What version is this? */  120
int  (*key proc)();  /* Procedure to grab password */
u int  datalen;  /* length of that data packet */
u char  *data;  /* A data packet */
u char  cookie[4];  /* this is a timestamp of the run */
char  cli encrypt;  /* Encrypt the session? */
char  srv encrypt;  /* Encrypt the session? */
char  obtain ticket;  /* Did we write a ticket to a file? */
char  principal[ANAME SZ];  /* principal’s name */
char  instance[INST SZ];  /* instance name */
char  realm[REALM SZ];  /* principal’s realm */  130
char  hostinst[INST SZ];  /* Host’s kerberos name */

char hostrealm[REALM SZ]; /* Realm of host */

des cblock  *session;  /* Session Key (if we want it) */

struct sockaddr in  saddr;  /* Server’s IP Address */
KTEXT  tgt;  /* TGT ktext */
KTEXT  tkt2;  /* Second ticket (service or shared) */
KTEXT  rcmd;  /* rcmd ticket, once we have it */

} charon t;

#endif /* CHARON PROT H */  140

B.4 crn.h

/* crn.h −− Internal header file for the table function. This defines

* functions internal to crn.c *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/crn.h,v $

* $Author: warlord $ * */

10

#ifndef CRN H #define CRN H

#include <charon prot.h>

static int SendVersion(int mode, int version); static int SendVersionInfo(charon t *charon); static int SendInfoAndKDCPkts(charon t *charon); static int CheckStatus(charon t *charon);

static int ProcessVersion(charon t *charon);  20

static int ProcessInfo(charon t *charon); static int ProcessServerKDC(charon t *charon); static int DoProcessServerKDC(charon t *charon); static int ProcessClientKDC(charon t *charon); static int ProcessCliAuth(charon t *charon);

#endif

B.5 ia5.h

/* ia5.h −− This defines the functions available in ia5.h *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/ia5.h,v $

* $Author: warlord $ * */

#ifndef IA5 H  10

#define IA5 H

#include <sys/types.h>

/* This will convert an length of octets to the length that the IA5

* encoding will be, plus 1 */ #define IA5 LENGTH(a) (((a + 2)/3)*4 + 1)

20

extern int ia5 encode data(u char *data, u int datalen, char *encoded, u int *encodedlen);

extern int ia5 decode data(char *data, u int datalen, u char *decoded, u int *decodedlen);

#endif

B.6 krb prot.h

/* krb prot.h −− This contains stuff that is in the prot.h file inside

* the kerberos library. It is neccessary because the kerberos library

* does not the functionality I need, exactly. I have pulled out as little

* of the library as I needed to get my code to work. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/athena.mit.edu/user/w/a/warlord/Thesis/src/lib/RCS/krb prot.h,v $

* $Author: warlord $

*  10

*/

#ifndef KRB PROT H #define KRB PROT H

#define KRB PROT VERSION 4

extern int krbONE; #define HOST BYTE ORDER (* (char *) &krbONE)

20

#define pkt version(packet) (unsigned int) *(packet−>dat) #define pkt msg type(packet) (unsigned int) *(packet−>dat+1)

#define pkt a name(packet) (packet−>dat+2) #define pkt a inst(packet) \ (packet−>dat+3+strlen((char *)pkt a name(packet))) #define pkt a realm(packet) \ (pkt a inst(packet)+1+strlen((char *)pkt a inst(packet)))

30

#define pkt err code(packet) ( (char *) \ (packet−>dat+9+strlen((char *)pkt a name(packet)) + \ strlen((char *)pkt a inst(packet)) + \ strlen((char *)pkt a realm(packet))))

#define  AUTH MSG KDC REPLY  2<<1
#define  AUTH MSG ERR REPLY  5<<1

#endif /* KRB PROT H */

40

B.7 parser.h

/* parser.h −− This file contains prototypes for the procedures in parser.c *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/athena.mit.edu/user/w/a/warlord/Thesis/src/lib/RCS/parser.h,v $

* $Author: warlord $ * */

#ifndef PARSER H  10

#define PARSER H

#include <sys/types.h> #include <charon prot.h>

extern void int2octet(int num, u char *octet); extern void octet2int(u char *octet, int *num); extern int info2octet(charon t *charon, u char **data, u int *datalen); extern int octet2info(u char *data, charon t *charon);

extern int octet2ktext(u char *octet, int datalen, KTEXT pkt);  20

#endif

B.8 rpc.h

/* rpc.h −− This contains the prototypes for functions in rpc.c *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/rpc.h,v $

* $Author: warlord $ * */

#ifndef RPC H  10

#define RPC H

#include <stdio.h> #include <sys/types.h> #include <charon prot.h>

extern int crn rpc put ktext(KTEXT pkt, int srv); extern int crn rpc get ktext(KTEXT pkt); extern int crn rpc sendauth(KTEXT auth);

extern int crn rpc senddat(MSG DAT *data);  20

extern int crn rpc getdat(MSG DAT *data); extern int crn get status(); extern int crn read status(char *msg, u int len); extern void crn send success(); extern void crn send error(char *msg); extern int crn wait for data(u char **data, u int *datalen, u char *datatype); extern int crn send data(u char *data, u int datalen, u char datatype); extern int crn read data(u char *data, u int *datalen, u char *datatype);

#endif  30

B.9 table.h

/* table.h −− this header file defines a dispatch table for Charon. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/table.h,v $

* $Author: warlord $ * */

#ifndef TABLE H  10

#define TABLE H

#include <stdio.h> #include <charon prot.h> #include "crn.h"

/* A state is an array of functions and integers, with an index of

* a packet type. The next state is a number which says which state

* it should go to next on a valid return (i.e., 0) from the function.

*/  20

typedef struct state t {

int  (*entry)();  /* function to call when entering this state */
int  maxstate;  /* Highest state in this table */

int (*fcn[MAX NUM PKTS])(); /* functions to process packet */ int next state[MAX NUM PKTS]; } state t;

/* A dispatch table is an array of state t, where the index is the

* state or the FSM. We need one of these for each version, for

* each side of the protcol. There should be 16 offsets into the  30

* array: one for each of the 16 possible packet types. Use NULL

* for an invalid packet type (error). */ typedef state t *d table;

/* A version table is an array of dispatch tables for different versions

* of the protocol. We need one of these for each side of the protocol. */ typedef d table *v table;

40

/*************************************************************/ /* Below this line is a set of definitions for the protocol.

* It is in the form of a Finite State Machine. Each version

* has its own FSM, defined as an array of states. */

/* States for the client side of version 2 */ static state t cv2 d table[] = { { /* state 0 */

SendVersionInfo,  50

3,

{CheckStatus, NULL, ProcessVersion, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},

{0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},

{ /* state 1 */

NULL,

3,  60

{CheckStatus, NULL, NULL, ProcessInfo, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},

{1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, { /* state 2 */ NULL, 3,

{CheckStatus, NULL, NULL, NULL,  70

ProcessServerKDC, NULL, NULL, NULL,

NULL, NULL, NULL, NULL,

NULL, NULL, NULL, NULL}, {2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}

};

/* States for the server side of version 2 */ static state t sv2 d table[] = { { /* state 0 */

NULL,  80

2, {CheckStatus, ProcessVersion, NULL, NULL,

NULL, NULL, NULL, NULL,

NULL, NULL, NULL, NULL,

NULL, NULL, NULL, NULL}, {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},

{ /* state 1 */ SendInfoAndKDCPkts,

2,  90

{CheckStatus, NULL, NULL, NULL, NULL, ProcessClientKDC, ProcessCliAuth, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},

{1, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0}},

{ /* state 2 */ NULL, 2,

{CheckStatus, NULL, NULL, NULL,  100

NULL, NULL, ProcessCliAuth, NULL,

NULL, NULL, NULL, NULL,

NULL, NULL, NULL, NULL}, {2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0}}

};

/* Client Version Table */ static d table c v table[] = {NULL, NULL, cv2 d table, NULL};

/* Server Version Table */  110

static d table s v table[] = {NULL, NULL, sv2 d table, NULL};

/* A command table is an array of version tables, for the client side

* and server side of the protocol. There is only one of these! This

* assumes that a client is 0, and a server is 1. */ static v table command table[] = {c v table, s v table};

#endif /* TABLE H */

86

Appendix C

Ckinit (src/ckinit)

C.1 ckinit.c

/* ckinit.c −− This source file is for ckinit, a charon initialization

* program that uses /dev/tty as the I/O stream. It is used to get

* new kerberos tickets while someone is already logged in using the

* charon protocol. *

* Created by: Derek Atkins <warlord@MIT.EDU> *

* $Source: /afs/net.mit.edu/user/warlord/Thesis/src/ckinit/RCS/ckinit.c,v $

* $Author: warlord $

*  10

*/

#if !defined(lint) && !defined(SABER)

static char ckinit c[] = "$Id:  ckinit.c,v 1.9 93/05/06 01:06:38 warlord Exp $";

#endif

#include <stdio.h> #include <string.h> #include <signal.h>

#include <sys/types.h>  20

#include <pwd.h> #include <krb.h> #include <charon.h>

#if defined(sun) && defined(sparc) && defined(POSIX) /* SOLARIS */ #define ZERO(a,l) memset((char *)(a), 0, (l)) #else #define ZERO(a,l) bzero((char *)(a), (l)) #endif

30

static void *tty state = NULL; extern char *getenv();

static void usage(char *whoami) {

 fprintf(stderr, "Usage:  %s [-k realm_of_host] [-p principal]\n", whoami);
 fprintf(stderr, "\t[-f ticket_file] [-l lifetime]\n");

exit(1);  40

}

/* If we get a signal, we should do this, so that we can exit * cleanly. */ int error exit() { crn revert echo(tty state);

exit(1);  50

}

/* I should mention that a lot of this is based off of rkinit! *

* Effects: Get kerberos tickets for a user! Possible options to

* this program include: *

* −k == set the kerberos realm of this host

* −p == set kerberos principal (of form princ.inst@realm)

* −f == ticket file to store tickets 60

* −l == ticket lifetime *

* If no principal is passed in, then look in the ticket

* file for a name. If not there, then use username

* and NULL instance, and host−realm. *

* Use default lifetime if none is specified. */ main(int argc, char *argv[])

{  70

char *whoami; /* program name. */

char principal[MAX K NAME SZ]; /* principal name */ char a name[ANAME SZ]; /* These are principal, */ char inst[INST SZ]; /* instance, and the */ char realm[REALM SZ]; /* realm to use for tickets */ char hostrealm[REALM SZ]; /* realm of host */ char *debug file = NULL; /* place to put debug info */

char *ticketfile = NULL;  /* ticket file name */  80

int lifetime = DEFAULT TKT LIFE; /* ticket lifetime */ int i, status = 0; struct passwd *pwd; /* local passwd info */

/* Clear out these buffers */ ZERO(principal, sizeof(principal)); ZERO(a name, sizeof(a name)); ZERO(inst, sizeof(inst)); ZERO(realm, sizeof(realm));

ZERO(realm, sizeof(hostrealm));  90

/* Set the program name. */ if ((whoami = strrchr(argv[0], '/')) == NULL) whoami = argv[0]; else whoami++;

/* Deal with the command−line options */ for (i = 1; i < argc; i++) {

100

/* make sure that its an option! */ if (argv[i][0] != '-') usage(whoami);

switch(argv[i][1]) {

case 'k': /* realm */ if (++i >= argc)

usage(whoami);

strncpy(hostrealm, argv[i], sizeof(hostrealm) −1);  110

break; case 'p': /* principal */ if (++i >= argc)

usage(whoami); strncpy(principal, argv[i], sizeof(principal) −1); break;

case 'f': /* ticket file */

if (++i >= argc)  120

usage(whoami); ticketfile = argv[i]; break;

case 'l': /* lifetime */ if (++i >= argc)

usage(whoami); lifetime = atoi(argv[i])/5; if (lifetime = 0)

lifetime = 1;  130

else if (lifetime > 255)

lifetime = 255; break;

case 'd': /* debug */ if (++i >= argc)

usage(whoami); debug file = argv[i]; break;

140

default: usage(whoami); } /* switch */ } /* for */

if ((pwd = getpwuid(getuid())) == NULL) {

fprintf(stderr, "%s:  Cannot find your entry in the password file.\n");

exit(1); }

150

/* Make sure we have an a name, inst, and realm to pass in.

* (we really only need an a name, since the library will deal

* with everything else. */ if (! principal[0]) {

/* No principal passed in −− look in ticket file */ if ((status = krb get tf fullname(TKT FILE, a name, inst, realm)) != KSUCCESS) {

/* No ticket file. use username@hostrealm */  160

strncpy(a name, pwd−>pw name, sizeof(a name) − 1); strcpy(realm, hostrealm); } } else { if ((status = kname parse(a name, inst, realm, principal))

!= KSUCCESS) { fprintf(stderr, "%s: %s\n", whoami, krb err txt[status]); exit(1); }

}  170

tty state = crn kill echo(0);

/* trap a lot of signals */ signal(SIGHUP, error exit); signal(SIGINT, error exit); signal(SIGQUIT, error exit); signal(SIGKILL, error exit); signal(SIGTERM, error exit); if (debug file != NULL) crn rpc set debug file(debug file);

status = crn srv get tickets(a name, inst, realm, hostrealm, lifetime, ticketfile, 0, 0, 0);

crn revert echo(tty state);

return(status);

}  190

Bibliography

[1] E. J. Berkenbilt. Rkinit. Personal Communiqué, November 1989.

[2] J. G. Steiner, B. C. Neuman, and J. I. Schiller. Kerberos: An Authentication Service for Open Network Systems. In Usenix Conference Proceedings, pages 191–202, Dallas, Texas, February, 1988.

[3] J. Linn. Privacy Enhancement for Internet Electronic Mail: Part I: Message Encryption and Authentication Procedures. RFC 1421, February 1993.

[4] National Bureau of Standards, Washington, D.C. Data Encryption Standard, Federal Information Processing Standards Publication 46 edition, 1977. Government Printing Office.

[5] B. Clifford Neuman and Jennifer G. Steiner. Authentication of Unknown Entities on an Insecure Network of Untrusted Workstations. In Usenix UNIX Security Workshop Proceedings, pages 10–11, Portlan, Oregon, August, 1988.

[6] S. P. Miller, B. C. Neuman, J. I. Schiller, and J. H. Saltzer. Kerberos Authentication and Authorization System. Technical report, M.I.T. Project Athena, ”1985, 1986, 1987”.