Security

SSH Kerberos Authentication Using GSSAPI and SSPI

Kerberos authentication can be an effective safeguard against man-in-the-middle attacks. Glen implements Kerberos by way of two popular APIs.

February, 2006: SSH Kerberos Authentication Using GSSAPI and SSPI

Glen Matthews is a developer at a well-known Canadian software company, where he has been occupied with implementing a commercial version of SSH. He can be reached at GlenMatthews@Sympatico.Ca.

Given the growing need for secure and private data transfer, the Secure Shell protocol (SSH) offers a robust and flexible solution to the problem of protecting your communications. A major component of such a solution involves authentication of the client and in the best of all worlds, the server. Part of the flexibility of SSH is that it supports a variety of authentication methods. Of particular interest for large enterprises is authentication using the Kerberos protocol, not only because of the single sign-on capability, but also because it has a potential to preclude classic man-in-the-middle attacks on the SSH protocol. Because Kerberos is the primary authentication mechanism for Windows (postWindows 2000) and because it is supported on all UNIX-like platforms, Kerberos provides these capabilities today.

SSH is a complicated protocolthe Secure Shell IETF Working Group web page lists 16 Internet drafts (and as of this writing, no RFCs). The result of this complexity is a rich set of capabilities. In what follows, I describe how Kerberos authentication can be done using both the standard GSSAPI (Generic Security Services Application Programming Interface) and Microsoft's SSPI (Security Support Provider Interface).

To run the SSH protocol, you do the following:

(a) Negotiate encryption.

(b) Authenticate the user.

(c) Open and use channels.

Steps (a) and (b) result in an encrypted and authenticated tunnel, which is a container for the communications. Step (c)repeated within the tunnel as many times as desiredresults in a channel. Channels run within an encrypted tunnel and carry the traffic that would normally be carried on a standard TCP/IP socket. I won't go into communications over channels because the focus of this article is really on step (b)the authentication of the user of the tunnel.

The SSH Protocol: A Bird's Eye View

SSH provides an encrypted and authenticated communications tunnel between a client and server, usually on different machines. Within this tunnel, any number of virtual links called "channels" can be created. Although the initial purpose was to provide remote secure shells, other types of channels can be opened, including X11 channels, file transfer channels, and socket forwarding channels (port forwardingboth inbound and outbound).

To support all of this, SSH uses a packetized binary protocol on the wire. The basic structure of an encrypted SSH packet consists of a short header, followed by the message type, followed by the payload, and finally followed by some Message Authentication Code (MAC) bytes (see Figure 1). The payload encapsulates the traffic of the protocols previously mentioned, similar to how IP encapsulates other protocols. Of course, SSH packets themselves are usually encapsulated within TCP/IP packets.

As part of negotiating the tunnel encryption keys between client and server, the server's public key is exchanged with the client. This key (and the associated private key) enables public key cryptography such as digital signatures to be performed between the two parties. At this point, the SSH protocol is most vulnerable. If an attacker is positioned in between the client and the server, the attacker can pose as the server to the client using some bogus key, and the client machine might not be able to tell the difference; the attacker would simply relay messages between the server and the client, pretending to each side to be the other side (see Figure 2). In a sense, the attacker would be acting as a malicious proxy. This is the classic man-in-the-middle attack.

In order to continue, you must determine if the server's private key is correct. If the client machine has the key available through some mechanism (possibly a previous connection), then it can be compared and the authenticity of the server can be determined. If not, you are customarily asked to decide whether to accept the key or not. Unfortunately, you have little upon which to base such a decision for first-time connections, and so you customarily reply "yes."

The reason for this weakness is that when the SSH protocol was first described, public-key infrastructures were poorly deployed. Although this was a logical place to embed some means of server authentication, there was nothing practical available. I'll come back to this point shortly.

Once the server key has been negotiated, user authentication is then negotiated and carried out. In the case of a password authentication (for example), the client would send a login ID and password within an authentication packet, and the server would respond with either success or failure.

This is a logical place to do authentication using Kerberos. "GSSAPI Authentication and Key Exchange for the Secure Shell Protocol" [4] is an Internet draft Request for Comments (RFC) describing an authentication mechanism using Kerberos based on the GSSAPI. Using the same SSH protocol packets, Microsoft's SSPI can be called because one of the (several) security packages that it supports is Kerberos.

A very interesting option described in this draft RFC is that of authenticated Diffie-Hellman key exchange. This mechanism performs a Kerberos-authenticated key exchange at the step above where the server key would be negotiated. The client first authenticates itself to the server via Kerberos. If mutual authentication (an option) is selected, then the server authenticates itself to the client (note that this is more than simply comparing keys). This allows a first-time connection to a new server to be completely safe from the man-in-the-middle because only the server can satisfactorily authenticate itself to the client via Kerberos. An interesting side effect is that the actual user authentication then becomes a formality because the user has already been authenticated in the key exchange step.

Kerberos Authentication: Another Bird's Eye View

In order to understand how the man-in-the-middle attack is defeated with mutual authentication, I need to review how Kerberos authentication is carried out. Kerberos deals with binary objects called "tickets"and the name is suggestive of the role that they play.

As a client, you need to acquire a ticket-granting ticket (TGT) initially by authenticating yourself to the Kerberos KDC (Key Distribution Center) using a KRB_AS_REQ (Kerberos Authentication Service Request) message. (Note that the KDC holds all of the user and server private keys in order to encrypt tickets using these keys.) Later, during the acquisition of service tickets, the TGT proves to the KDC that you have authenticated yourself when it is presented. A service ticket is like a letter of introduction in that it automatically authenticates you to the network service for which it was acquired, without needing to provide other credentials. Most importantly, the software that you are using (such as SSH) can acquire these subsequent service tickets automatically on your behalf, assuming that you have a previously acquired TGT.

The reply back from the KDC contains a generated session key, as well as a separate message (the TGT) containing this key, and your authorization data, encrypted with the KDC's master key. Only the KDC can decrypt the TGT. The entire reply is encrypted with your private key, so that only your client software can decrypt this message. The client software on your machine stores your session key as well as the TGT into a local cache.

To request a service ticket for some service, such as a login session for a remote host, you send a KRB_TGS_REQ (Kerberos Ticket Granting Service Request) message to the KDC. Included in that message is the TGT that you have obtained. The KDC's encrypted reply contains your service session key (the key that you will use to encrypt messages between you and the service) and a ticket for sessions with that service encrypted with its private key. The service key and ticket are also cached locally.

To establish your session with the login service, you now send a KRB_AP_REQ (Kerberos Application Request) message. This contains your service ticket, an authenticator containing a timestamp that is encrypted with the service session key, and a flag indicating whether mutual authentication is desired. When the login service receives this message, it decrypts the service ticketonly it can do this. It then decrypts the authenticator (which can only be done if it can decrypt the service ticket!) and checks the authentication data and timestamp to authenticate you (or not). If the mutual authentication flag is set, it encrypts the timestamp from your authenticator message using the service session key and sends this back in a KRB_AP_REP (Kerberos Application Reply) message. Finally, when you receive this message, your client software decrypts this, and if the timestamp is the one you originally sent, then the server has authenticated itself to you: Mutual authentication is then complete.

This cleverly defeats the man-in-the-middle attack because the information used for mutual authentication is encrypted using the session key, known only to the client and server. Of course, if the attacker has obtained this key by breaking into the KDC and monitoring your network traffic, then all bets are offand you'll likely have larger problems!

SSH Authentication Using Kerberos

By now it should be clear that Kerberos authentication is a very desirable feature. In SSH, this authentication proceeds by way of the GSSAPI or the SSPI interface. There are two methods of authentication: One runs over the SSH User Authentication protocol and the other runs over the SSH Transport protocol (this is the authenticated Diffie-Hellman key exchange previously mentioned).

"GSSAPI Authentication and Key Exchange for the Secure Shell Protocol" [4] describes a sequence of packets that must be sent when authentication starts. The data that is put in these packets comes from the output of the GSSAPI or SSPI function callsthose calls will be discussed later.

If you are authenticating using SSH User Authentication protocol, you would first send the SSH_MSG_USERAUTH_REQUEST message (see Figure 3). When this message is sent, requesting Kerberos authentication (gssapi-with-mic is the protocol name for it), a list of supported objects called "mechanism OIDs" (Object Identifiers) is included; see the sidebar "OIDs, GSSAPI, and SSH."

The Kerberos mechanism OID (1.2.840.113554.1.2.2) is required. The GSSAPI call gss_indicate_mechs() returns a list of the OIDs of the supported security mechanisms. However, SSPI doesn't have an equivalent to this call. The returned packet from the SSPI InitializeSecurityContext() call contains the mechanism OID. "RFC 2743: Generic Security Service Application Program Interface, Version 2, Update 1" [5] (Section 3.1: Mechanism-Independent Token Format) explains the formatting of this packet. The data of interest is at the beginning:

0x608204BE06092A864886F712010202

This can be decoded as follows:

0x60TAG for packet type.

0x8Xindicates the length of the entire packet is > 128 bytes.

0xX2indicates the length of the following byte count; here it's 2.

0x04BEthe length of the packet.

The remaining data of interest to us is: 0x'06092A864886F712010202'. This decodes as follows:

0x06Tag for OBJECT IDENTIFIER.

0x09OID length.

0x2A 86 48 86 F7 12 01 02 02encoded OID bytes (see sidebar).

In this manner, the correct mechanism OID can be acquired. (The alternative is to hard-code the value.)

The server then replies with SSH_MSG_USERAUTH_GSSAPI_ RESPONSE, in which it lists the mechanisms that it knows about. What follows then is a sequence of SSH_MSG_USERAUTH_GSSAPI_ TOKEN pairs, first from the client and then from the server, until the client receives GSS_S_COMPLETE from GSSAPI (or SEC_E_OK from the SSPI). You then send SSH_MSG_USERAUTH_GSSAPI_MIC and should receive SSH_MSG_USERAUTH_SUCCESS.

If you are using the authenticated Diffie-Hellman key exchange, then the process is similar, but the data transmitted is sent with different message numbers (see Figure 4). There are several differences. First, there is no negotiation of the mechanism OIDthis is implicit in the name of the key exchange algorithm. Second, the sequence of message pairs SSH_MSG_KEXGSS_CONTINUE ends with the server sending the last message. Third, the Message Integrity Code (MIC) message is sent later, under the SSH User Authentication protocol.

Establishing a Security Context

The first step for both GSSAPI and SSPI is to load the code into memory. In the case of the GSSAPI, the required file is gssapi32.dll while in the case of SSPI, the file is security.dll. Once loaded, GetProcAddress() can be called against the GSSAPI library to create a dispatch table. SSPI has a built-in call, InitSecurityInterface(), which does this for you. This step ensures that your code is not tightly bound to a particular version of either interface.

The next step is to generate the service principal name. A principal may be a user, a program, or a machine. In this case, the service principal is the computer account corresponding to the service to be accessed. In the GSSAPI, names of principals are stored in a gss_name_t object. The string format thus needs to be imported via gss_import_name(), using code similar to that in Listing 1.

pAddr is a pointer to the structure filled in via GetProcAddress() calls as mentioned earlier. Note that the value of ServiceName contains "host" as a literal string. This represents the service principal; the full service principal name is in the format:

host/fully-qualified-domain-name@REALM-NAME

The Kerberos realm is the collection of machines that have service entries in the KDCit usually corresponds to domain names but is not required to do so.

When using the SSPI, the client can generate the DNS format of the server principal name by issuing the Active Directory function DsGetDcName(). Names in the SSPI are passed as strings (see Listing 2).

Once the mechanism OID has been negotiated as previously described (either via the SSH_MSG_USERAUTH_REQUEST message or via the key exchange method name), the next thing to do is send token output to the server from either the InitializeSecurityContext() or gss_init_security_context(). The packets that are sent are either SSH_MSG_USERAUTH_GSSAPI_TOKEN or SSH_MSG_KEXGSS_ INIT and depend on which method is used for authentication.

The GSSAPI call is shown in Listing 3. min_stat is a secondary return code. The initial credential handle parameter (the second parameter) is set to GSS_C_NO_CREDENTIAL, indicating the default credential because it is assumed that the user has acquired credentials, either via login or some other mechanism (for example, kinit.exe). gss_context is the context handle, set initially to GSS_C_NO_ CONTEXT, and set to a buffer address by the routine if successful. target_name contains the imported service principal name, while OID contains the Kerberos OID. req_flags can contain a number of flags: The one of interest here is GSS_C_MUTUAL_FLAG (which requires mutual authentication between the server and the client, and which is the mechanism that eliminates the man-in-the-middle attack). Skipping over the next two parameters, there is input_token_ptr (the input token from the server). This is not available on the first call, but is on subsequent callson the first call, it is set to a null value. stok is the output value from gss_init_security_context(). ret_flags are flags returned by the GSSAPIfrom this the client can tell if mutual authentication is allowed.

The SSPI call is shown in Listing 4. Unlike the GSSAPI, some initial credentials are neededthe AcquireCredentialsHandle() call can be used to get OUTBOUND credentials (no user interaction requiredthis would have been acquired at login). For the first call, the handle of the partially formed context (the second parameter) does not need to be supplied. szMSServicePrincipal is an ANSI string containing the service principal name. Unlike the GSSAPI, there is no need to import this into an internal format. grfContextRequirements contains flags as in the GSSAPImutual authentication is requested. SECURITY_ NETWORK_DREP requests network data representation (network byte order). sbdi is the pointer to the input buffers (NULL on the first call). hctx receives the new context handle if the call is successful. sbdo points to the output buffers and contains the token to be sent to the server. ulCtxAttrs contains the output flags, as in the GSSAPI. Finally, Expiry receives the lifespan of the context.

When the first token is received, it is sent to the server, which then replies with a message. The token in this message is passed back into InitializeSecurityContext() or gss_init_security_context() depending on the interface being used. Once the return code indicates that the authentication is complete, almost everything is done. Otherwise, the process is repeated until complete or until authentication fails.

What remains is to send a message containing a signed cryptographic hash of several string values, both static and dynamic, computed during the authentication process. gss_get_mic() (GSSAPI) or MakeSignature() (SSPI) are used to do this.

The GSSAPI code for this is shown in Listing 5. micdata is of type gss_buffer_desc and has a value and length component. Using the GSS context (gss_context) and the data in micdata, gss_get_mic() returns the signed message in msg_token. This is then sent to the server as the final step in the authentication process.

Likewise, the (more complex) SSPI code is displayed in Listing 6. Here the same basic steps as in the GSSAPI case are being followed, except that MakeSignature() requires a more complicated setup with the SecBuffer array of structures. QueryContextAttributes() gives us the size of the output buffer used for MakeSignature(). The output from this routine is in pSignature, with the returned length in rgsb[1].cbBuffer. (The SecBuffer and SecBufferDesc typedefs are included for claritynormally they would be included from a header file.)

In the SSH User Authentication protocol, the message SSH_MSG_USERAUTH _GSSAPI_MIC is used to send this signed hash. When using authenticated key exchange, it is the actual SSH_MSG_USERAUTH _REQUEST message that contains thisthe user has already been authenticated and this then is the final step.

I won't discuss other API calls in the GSSAPI and the SSPI. Table 1 illustrates a subset of the available calls and shows the mapping between them. One point to mention is that once authenticated, the GSSAPI or the SSPI can be used for encryption and decryption of messages via the respective pairs of routines gss_wrap()/gss_unwrap() or EncryptMessage()/DecryptMessage(). However, in the SSH protocol, these are not needed because separate encryption algorithms are negotiated via the protocol itself. The context can be discarded once authentication has been completed.

Summary

Kerberos authentication in SSH is a very useful capability that closes a well-known, albeit accepted, weakness in the protocol. It has the added advantage of enabling a single sign-on capability with respect to SSH connections on Windows platforms, because the underlying Windows authentication model is Kerberos. For complete working examples of the GSSAPI client and server applications, look at the MIT Kerberos distributions. A modified version of this sample code that uses the SSPI is included in the Windows 2000 SDK under samples\WinBase\ security\win2000\GSS.

References

(Note: Some of these drafts, and in particular numbers [2] and [3], may soon become RFCs. When this occurs, they should be available via a lookup at http://www.ietf.org/rfc.html. The SSH Transport Layer Protocol draft is slated to become RFC 4253, and the SSH Authentication Protocol draft is slated to become RFC 4252.)

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!