RFC 7546

Structure of the Generic Security Service (GSS) Negotiation Loop

๐Ÿ“… published May 2015, from ietf

Structure of the Generic Security Service (GSS) Negotiation Loop ๐Ÿฆ‹

RFC 7546 ๐Ÿ’– is Structure of the Generic Security Service (GSS) Negotiation Loop, written by B. Kaduk from MIT ๐ŸŽ€. It's Informational (so it's not a Standards Track spec, just a helpful guide โœจ), dated May 2015, with ISSN 2070-1721. The header doesn't list anything it obsoletes or updates, so no drama there ๐Ÿ’….

Abstract

okay so this doc spells out the generic shape of the negotiation loop used to set up a Generic Security Service (GSS) security context between an initiator and an acceptor ๐Ÿ”ฅ. It shows how the control flow of that loop works for both sides, including what goes wrong, and it points out the spots where each application has to decide its own behavior ๐Ÿงš.

Status of This Memo

this is not an Internet Standards Track specification ๐Ÿ™ˆ. It's published for information only ๐Ÿ’œ.

It's a product of the Internet Engineering Task Force (IETF) ๐ŸŒธ and it represents the consensus of the IETF community โœจ. It got public review and was approved for publication by the Internet Engineering Steering Group (IESG) ๐Ÿ‘‘. Not every IESG-approved doc is eligible for any level of Internet Standard, so check Section 2 of RFC 5741 if you're curious ๐Ÿค“.

For the current status, any errata, and how to send feedback, head to http://www.rfc-editor.org/info/rfc7546 ๐Ÿซถ.

Copyright (c) 2015 IETF Trust and the persons identified as the document authors ๐ŸŒˆ. All rights reserved ๐Ÿ’….

This document is covered by BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents, which you can find at http://trustee.ietf.org/license-info as they stood on the publication date ๐Ÿ“œ. Please actually read those, bestie, because they spell out your rights and restrictions for this document ๐Ÿ™ˆ. Any Code Components pulled out of this document must include the Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions, and they come with no warranty, as the Simplified BSD License says ๐Ÿ’–.

Table of Contents

   1. Introduction ....................................................2
   2. Application Protocol Requirements ...............................3
   3. Loop Structure ..................................................4
      3.1. Anonymous Initiators .......................................5
      3.2. GSS_Init_sec_context .......................................5
      3.3. Sending from Initiator to Acceptor .........................6
      3.4. Acceptor Sanity Checking ...................................6
      3.5. GSS_Accept_sec_context .....................................7
      3.6. Sending from Acceptor to Initiator .........................8
      3.7. Initiator Input Validation .................................9
      3.8. Continue the Loop ..........................................9
   4. After Security Context Negotiation ..............................9
      4.1. Authorization Checks ......................................10
      4.2. Using Partially Complete Security Contexts ................10
      4.3. Additional Context Tokens .................................11
   5. Sample Code ....................................................12
      5.1. GSS Application Sample Code ...............................13
   6. Security Considerations ........................................19
   7. References .....................................................20
      7.1. Normative References ......................................20
      7.2. Informative References ....................................20
   Acknowledgements ..................................................21
   Author's Address ..................................................21

1. Introduction โœจ

"Generic Security Service Application Program Interface Version 2, Update 1" [RFC2743] gives you a generic interface for security services ๐Ÿ’–. It's an abstraction layer sitting on top of the underlying security mechanisms an application might use ๐Ÿงš. A GSS initiator and acceptor swap messages, called "tokens", until a security context gets established ๐Ÿ”ฅ. That context lets each party authenticate the other, pass confidential and/or integrity-protected messages back and forth, and generate identical pseudorandom bit strings on both sides [RFC4401], plus more ๐Ÿฆ‹.

During context establishment, the security context tokens get exchanged synchronously, one at a time, and the initiator always sends the first one ๐ŸŽ€. How many tokens it takes depends on the underlying mechanism and on the properties you want the context to have, and in general the application doesn't know that number ahead of time ๐Ÿ™ˆ. So the application's control flow has to include a loop that keeps trading GSS security context tokens, and that loop ends either when the context is successfully established or when an error shows up ๐Ÿ’…. The GSS-API and its security mechanisms define the format and encoding of the context tokens themselves ๐Ÿ‘‘. The application protocol, though, must specify the framing so the application can tell which octet strings count as GSS security context tokens and hand them to the GSS-API implementation at the right moments โœจ.

The GSS-API C-bindings [RFC2744] include some example code for this kind of negotiation loop ๐ŸŒธ. The catch is that the example doesn't say what the application should do with unexpected or error conditions ๐Ÿ˜ญ. Because of that, each application protocol spec has had to write out its own GSS negotiation loop, error handling and all, on a per-protocol basis (see [RFC4462], [RFC3645], [RFC5801], [RFC4752], and [RFC2203]) ๐Ÿ’€. That's a ton of duplicated effort, and the specs go into wildly different levels of detail and cover different error conditions ๐Ÿ™ƒ. So it's way better to describe the structure of the GSS negotiation loop, including error conditions and token passing, in one spec that other docs can just reference instead of retyping the loop every time ๐Ÿซถ. That's exactly the job this document is here to do ๐Ÿ’œ.

The requirements you need to run a GSS negotiation loop correctly are basically all in [RFC2743], but they're scattered all over the place ๐Ÿงฉ. This document gathers them into one spot so implementers have an easier time ๐Ÿ“. The normative requirements still live in [RFC2743] though ๐Ÿ“œ. A few spots here also mention extra behavior that's useful for applications but isn't mandated by [RFC2743] โœจ.

2. Application Protocol Requirements ๐ŸŒท

part of the point of this document is to guide new application protocols using the GSS-API, and the new application software that uses those protocols ๐Ÿฆ‹. Here's the list of features a protocol like that needs or would find useful ๐Ÿ’…:

  • Protocols require a way to frame and identify security context negotiation tokens during the GSS negotiation loop ๐ŸŽ€.
  • Error tokens should generally also get special framing, because the recipient may have no other way to tell unexpected error context tokens apart from per-message tokens ๐Ÿ˜ฑ.
  • Protocols may benefit from a generic means of indicating an error, possibly with a descriptive string, to be used if error tokens aren't available or aren't usable because of the application protocol's constraints ๐ŸŒˆ.
  • A protocol may use the negotiated GSS security context for per-message operations ๐Ÿ’–. In those cases, the protocol needs a way to frame and identify those per-message tokens and to say what their contents are ๐Ÿงš. For example, a protocol message may come along with the output of GSS_GetMIC() over that message โœจ. The protocol must identify where that Message Identity Code (MIC) token sits and how big it is, say that it is a MIC token, and say which cleartext it corresponds to ๐Ÿ”ฅ.
  • Applications are responsible for authorizing the authenticated peer principal names that the GSS-API supplies ๐Ÿ‘‘. Those names are mechanism specific and may come from a different part of a federated identity scheme ๐ŸŒธ. Application protocols may need to supply extra information to support authorizing access to a given resource, like the "username" parameter in Secure Shell version 2 (SSHv2) ๐Ÿซถ.

3. Loop Structure ๐ŸŽ€

The loop kicks off with the initiator (the appropriately named one ๐Ÿ’…), who calls GSS_Init_sec_context() with an empty, zero-length input_token and a fixed set of input flags that describe the attributes you want the security context to have ๐Ÿฆ‹. Between calls during the loop, the initiator should not change any of the input parameters to GSS_Init_sec_context(), with two exceptions ๐Ÿ™ˆ. The first is input_token, which will hold a message from the acceptor after the initial call โœจ. The second is input_context_handle, which must be whatever was returned in output_context_handle by the previous call to GSS_Init_sec_context() (or GSS_C_NO_CONTEXT for the very first call) ๐Ÿ’œ. In the C bindings there's just one read/modify context_handle argument, so the same variable should be passed in on every call in the loop ๐Ÿ“. [RFC2743] only requires the claimant_cred_handle argument to stay constant across all the calls in the loop, but the other non-excepted arguments should stay fixed too if you want reliable operation ๐ŸŒˆ.

The next subsections walk through the different steps of the loop, without worrying about whether a given call to GSS_Init_sec_context() or GSS_Accept_sec_context() is the first one in the loop ๐Ÿงš.

3.1. Anonymous Initiators ๐Ÿ•ถ๏ธ

If the initiator wants to be anonymous, it sets the anon_req_flag input to GSS_Init_sec_context() ๐Ÿ™ˆ. On any non-error return from GSS_Init_sec_context() (meaning the major status is GSS_S_COMPLETE or GSS_S_CONTINUE_NEEDED), the initiator must verify that the anon_state output from GSS_Init_sec_context() is true before it sends the security context token to the acceptor ๐Ÿ”ฅ. Skipping that check could make the initiator lose its anonymity ๐Ÿ˜ญ. Be careful about this one, bestie ๐Ÿ’€.

3.2. GSS_Init_sec_context โœจ

okay so bestie, the initiator calls GSS_Init_sec_context() ๐Ÿ’… using the input_context_handle for the current security context that's being established, its fixed set of input parameters, and the input_token it got from the acceptor (if this isn't the first trip around the loop) ๐Ÿ”. The presence or absence of a nonempty output_token, plus the value of the major status code, tell the initiator what to do next. It's giving decision tree ๐ŸŒณโœจ

o If the major status code is GSS_S_COMPLETE and the output_token is empty, then the context negotiation is fully complete and ready for use by the initiator with no further actions. ๐ŸŽ€ slay, we're done!

o If the major status code is GSS_S_COMPLETE and the output_token is nonempty, then the initiator's portion of the security context negotiation is complete but the acceptor's is not. The initiator MUST send the output_token to the acceptor so that the acceptor can establish its half of the security context. ๐Ÿฆ‹ the acceptor still needs its turn, so send that token over!

o If the major status code is GSS_S_CONTINUE_NEEDED and the output_token is nonempty, the context negotiation is incomplete. The initiator MUST send the output_token to the acceptor and await another input_token from the acceptor. ๐ŸŒธ not done yet, so send it and wait for the reply.

o If the major status code is GSS_S_CONTINUE_NEEDED and the output_token is empty, the mechanism has produced an output that is not compliant with [RFC2743]. ๐Ÿ™ˆ However, there are some known implementations of certain mechanisms such as NT LAN Manager Security Support Provider [NTLMSSP] that do produce empty context negotiation tokens. For maximum interoperability, applications SHOULD be prepared to accept such tokens and SHOULD transmit them to the acceptor if they are generated. ๐Ÿซถ ngl this one's a bit of a messy real-world situation, but the advice is to be ready for it!

o If the major status code is any other value, the context negotiation has failed. If the output_token is nonempty, it is an error token and the initiator SHOULD send it to the acceptor. If the output_token is empty, then the initiator SHOULD indicate the failure to the acceptor if an appropriate application-protocol channel to do so is available. ๐Ÿ˜ญ yikes, it failed, but still try to let the acceptor know!


3.3. Sending from Initiator to Acceptor ๐Ÿฆ‹

so the establishment of a GSS security context between initiator and acceptor requires some communication channel to swap the context negotiation tokens. The GSS spec doesn't pick the channel for you ๐Ÿ™…โ€โ™€๏ธ. It could be a dedicated TCP channel, a UDP-based Remote Procedure Call (RPC) protocol, or literally any other sort of channel. In lots of cases the channel is multiplexed with non-GSS application data, so the application protocol MUST always provide some way to identify the GSS context tokens (e.g., length and start location) and pass them through to the mechanism accordingly. ๐Ÿงš gotta be able to tell which bits are the tokens!

The application protocol MAY also include a facility for indicating errors from one party to the other, which can be used to convey errors resulting from GSS-API calls when appropriate (such as when no error token was generated by the GSS-API implementation). Note that GSS major and minor status codes are specified by language bindings, not the abstract API. Sending a major status code and optionally the display form of the two error codes may be the best that can be done in this case. ๐Ÿ’€ the best we can do sometimes, honestly.

However, even if there's a communication channel, that doesn't automatically mean it's appropriate for the initiator to indicate errors. For example, if the acceptor is a stateless or near-stateless UDP server, there is probably no need for the initiator to explicitly indicate its failure to the acceptor. Conditions like this can be treated in individual application protocol specifications. ๐ŸŒˆ each protocol gets to decide its own vibe!

If a regular security context output_token is produced by the call to GSS_Init_sec_context(), the initiator MUST transmit this token to the acceptor over the application's communication channel. If the call to GSS_Init_sec_context() returns an error token as output_token, it is RECOMMENDED that the initiator transmit this token to the acceptor over the application's communication channel. ๐Ÿ‘‘


3.4. Acceptor Sanity Checking ๐Ÿง

the acceptor's half of the negotiation loop gets triggered when it receives a context token from the initiator. Before calling GSS_Accept_sec_context(), the acceptor MAY find it useful to do some sanity checks on the state of the negotiation loop. โœจ

If the acceptor receives a context token but wasn't expecting one (for example, if the acceptor's previous call to GSS_Accept_sec_context() returned GSS_S_COMPLETE), this is probably an error condition, which means the initiator's state is invalid. See Section 4.3 for some exceptional cases. It's likely appropriate for the acceptor to report this error to the initiator via the application's communication channel. ๐Ÿ™ƒ

If the acceptor is expecting a context token (e.g., if the previous call to GSS_Accept_sec_context() returned GSS_S_CONTINUE_NEEDED) but doesn't get one within a reasonable amount of time after sending the previous output_token to the initiator, the acceptor SHOULD assume that the initiator's state is invalid (timeout) and fail the GSS negotiation. Again, it's likely appropriate for the acceptor to report this error to the initiator via the application's communication channel. โฐ ghosted, rip ๐Ÿ’”


3.5. GSS_Accept_sec_context ๐Ÿ’–

the GSS acceptor responds to the initiator's moves, so there SHOULD always be a nonempty input_token to calls to GSS_Accept_sec_context(). The input_context_handle parameter will always be given as the output_context_handle from the previous call to GSS_Accept_sec_context() in a given negotiation loop, or GSS_C_NO_CONTEXT on the first call. The acceptor_cred_handle and chan_bindings arguments SHOULD stay fixed over the course of a given GSS negotiation loop. [RFC2743] only requires that the acceptor_cred_handle remain fixed throughout the loop, but the chan_bindings argument should also remain fixed for reliable operation. ๐Ÿ”

The GSS acceptor calls GSS_Accept_sec_context(), using the input_context_handle for the current security context being established and the input_token received from the initiator. The presence or absence of a nonempty output_token and the value of the major status code are the indicators for how to proceed:

o If the major status code is GSS_S_COMPLETE and the output_token is empty, then the context negotiation is fully complete and ready for use by the acceptor with no further actions. ๐ŸŽ‰

o If the major status code is GSS_S_COMPLETE and the output_token is nonempty, then the acceptor's portion of the security context negotiation is complete but the initiator's is not. The acceptor MUST send the output_token to the initiator so that the initiator can establish its half of the security context. ๐Ÿฆ‹

o If the major status code is GSS_S_CONTINUE_NEEDED and the output_token is nonempty, the context negotiation is incomplete. The acceptor MUST send the output_token to the initiator and await another input_token from the initiator. ๐ŸŒธ

o If the major status code is GSS_S_CONTINUE_NEEDED and the output_token is empty, the mechanism has produced an output that is not compliant with [RFC2743]. ๐Ÿ™ˆ However, there are some known implementations of certain mechanisms such as NTLMSSP [NTLMSSP] that do produce empty context negotiation tokens. For maximum interoperability, applications SHOULD be prepared to accept such tokens and SHOULD transmit them to the initiator if they are generated. ๐Ÿซถ

o If the major status code is any other value, the context negotiation has failed. If the output_token is nonempty, it is an error token and the acceptor SHOULD send it to the initiator. If the output_token is empty, then the acceptor SHOULD indicate the failure to the initiator if an appropriate application-protocol channel to do so is available. ๐Ÿ˜ญ


3.6. Sending from Acceptor to Initiator ๐Ÿ“ฎ

the way the acceptor sends the context token back to the initiator depends on the communication channel between the two. For a synchronous bidirectional channel, it can just be another piece of data sent over the link. But for a stateless UDP RPC acceptor, the token will probably end up being sent as an RPC output parameter. Application protocol specifications will need to spell out how this works. ๐Ÿงฉ

If the application protocol has the initiator driving the application's control flow, it is particularly helpful for the acceptor to indicate a failure to the initiator, as mentioned in some of the above cases conditional on "an appropriate application-protocol channel to do so". ๐ŸŽ€

If a regular security context output_token is produced by the call to GSS_Accept_sec_context(), the acceptor MUST transmit this token to the initiator over the application's communication channel. If the call to GSS_Accept_sec_context() returns an error token as output_token, it is RECOMMENDED that the acceptor transmit this token to the initiator over the application's communication channel. ๐Ÿ’Œ


3.7. Initiator Input Validation ๐Ÿ”

the initiator's half of the negotiation loop gets triggered (after the first call) when it receives a context token from the acceptor. Before calling GSS_Init_sec_context(), the initiator MAY find it useful to do some sanity checks on the state of the negotiation loop. ๐Ÿ’โ€โ™€๏ธ

If the initiator receives a context token but wasn't expecting one (for example, if the initiator's previous call to GSS_Init_sec_context() returned GSS_S_COMPLETE), this is probably an error condition, which means the acceptor's state is invalid. See Section 4.3 for some exceptional cases. It MAY be appropriate for the initiator to report this error to the acceptor via the application's communication channel. ๐Ÿ™ƒ

If the initiator is expecting a context token (that is, the previous call to GSS_Init_sec_context() returned GSS_S_CONTINUE_NEEDED) but doesn't get one within a reasonable amount of time after sending the previous output_token to the acceptor, the initiator SHOULD assume that the acceptor's state is invalid and fail the GSS negotiation. Again, it MAY be appropriate for the initiator to report this error to the acceptor via the application's communication channel. โฐ


3.8. Continue the Loop ๐Ÿ”

if the loop is in neither a success nor a failure condition, then the loop MUST continue. Control flow returns to Section 3.2. ๐ŸŒŸ

4. After Security Context Negotiation โœจ

Okay bestie, so once a party has finished its half of the security context and done everything it owes the other side ๐Ÿ’…, the context is complete. That doesn't mean it's ready to use yet ๐Ÿ™ˆ The security context flags might not actually fit what the application needs. And in some cases the context can be ready before the negotiation is fully done, so see Section 4.2 ๐Ÿฆ‹

The initiator sets, as part of its fixed set of inputs to GSS_Init_sec_context(), values for all the defined request flag booleans ๐ŸŽ€ That includes deleg_req_flag, mutual_req_flag, replay_det_req_flag, sequence_req_flag, conf_req_flag, and integ_req_flag. Once negotiation is complete, the initiator must verify that the values of deleg_state, mutual_state, replay_det_state, sequence_state, conf_avail, and integ_avail (plus any extra flags added by extensions) from the last call to GSS_Init_sec_context() match what it asked for. If a flag was requested but isn't available, and the application protocol really needs that feature, the initiator must destroy the security context and not use it for application traffic ๐Ÿ’€

Application protocol specs that cite this document should say which context flags their application protocol requires ๐ŸŒธ

The acceptor gets these booleans as output: deleg_state, mutual_state, replay_det_state, sequence_state, anon_state, trans_state, conf_avail, and integ_avail, plus any additional flags added by extensions to the GSS-API ๐Ÿ‘‘ The acceptor must verify that any flags the application protocol needs are set. If a necessary flag isn't set, the acceptor must destroy the security context and not use it for application traffic. No exceptions, periodt ๐Ÿ’…

4.1. Authorization Checks

The acceptor gets the name of the initiator that authenticated during security context negotiation as one of the outputs of GSS_Accept_sec_context() ๐Ÿ” Applications need to run authorization checks on that received name (client_name in the sample code) before they give access to restricted resources. This is important because negotiation can succeed even when the client is anonymous, or when the client is from a different identity realm than the acceptor. It depends on the details of the mechanism the GSS-API uses to set up the context ๐Ÿ‘€ Acceptor applications can check which target name the initiator used, but the details are out of scope for this document. See Sections 2.2.6 and 1.1.5 of [RFC2743]. More info may be available in GSS-API Naming Extensions [RFC6680] ๐Ÿ“š

4.2. Using Partially Complete Security Contexts

For mechanism and flag combinations that need multiple token exchanges, the GSS-API specification [RFC2743] gives GSS_Init_sec_context() and GSS_Accept_sec_context() a prot_ready_state output value ๐Ÿฆ‹ It tells you when per-message operations are available. Honestly though, lots of mechanism implementations don't support this, and the security analysis of using it is pretty complicated. So it's not expected to be useful in most application protocols ๐Ÿ™ƒ

Mutual authentication, replay protection, and other services (if requested) are services that will be active when GSS_S_COMPLETE is returned. But they aren't necessarily active before the security context is fully established โš ๏ธ

4.3. Additional Context Tokens

Under some conditions, a party to a security context negotiation receives a context token after it has already finished the negotiation (aka after GSS_Init_sec_context() or GSS_Accept_sec_context() has returned GSS_S_COMPLETE) ๐Ÿงš Those tokens must be passed to GSS_Process_context_token() for processing. It might not always be necessary for a mechanism implementation to generate an error token on the initiator side, or for an initiator application to send that token to the acceptor. Those decisions are out of scope for this document. Both peers should always be ready to process these tokens, and application protocols should provide ways for them to be transmitted ๐Ÿ’–

These tokens can be security context deletion tokens, which get emitted when the remote party calls GSS_Delete_sec_context() with a non-null output_context_token parameter. They can also be error tokens, which get emitted when the remote party hits an error processing the last token in a security context negotiation exchange. Errors that happen while processing earlier tokens in the negotiation get sent as normal security context tokens, and are handled by GSS_Init_sec_context() or GSS_Accept_sec_context(), whichever is appropriate โœจ With GSS-API version 2, it is not recommended to use security context deletion tokens. So error tokens are expected to be the most common kind of additional context token for new application protocols ๐ŸŒˆ

GSS_Process_context_token() may show an error in its major_status field in two situations. One is when an error happens locally during token processing. The other is when an error happened on the peer and was sent over in an error token. See [Err4151] of [RFC2743] ๐Ÿ” No matter what major_status GSS_Process_context_token() returns, you should call GSS_Inquire_context() after processing the extra token. That lets you check the status of the security context and whether it can supply the features the application protocol needs ๐Ÿ‘‘

Right now, every token that should be handled by GSS_Process_context_token() leaves the security context effectively unusable ๐Ÿ˜ญ Future GSS-API extensions might let applications keep working after a call to GSS_Process_context_token(). If that happens, the outputs of GSS_Inquire_context() are expected to show whether it's safe to carry on. But since no such extensions exist yet (error tokens and deletion tokens both make the context essentially unusable), there's no guidance to give applications on this for now ๐Ÿคทโ€โ™€๏ธ

Even if GSS_Process_context_token() processes an error or deletion token that makes the context essentially unusable, the resources tied to the context must still eventually be freed with a call to GSS_Delete_sec_context() ๐Ÿงน This is the same thing you'd need to do if GSS_Init_sec_context() or GSS_Accept_sec_context() had returned an error while processing an input context token, and the input_context_handle was not GSS_C_NO_CONTEXT. RFC 2743 has some text on this that's a little ambiguous, but the best practice is to always call GSS_Delete_sec_context() ๐Ÿ’ฏ

5. Sample Code ๐Ÿ’ป

This section gives sample code for the GSS negotiation loop. It covers both a regular application and an application where the initiator wants to stay anonymous ๐ŸŽญ The code for the two cases is very similar, so the anonymous-specific additions are wrapped in a conditional check. You can ignore that check and the conditional code if anonymous processing isn't needed.

The communication channel between the initiator and acceptor is up to each application protocol. That means it's inherently unspecified at the GSS-API level, which can lead to examples that are less satisfying than you might want ๐Ÿ˜… For example, the sample code in [RFC2744] uses an unspecified send_token_to_peer() routine. Fully correct and general code to frame and transmit tokens needs a lot of error checking, and that would pull focus from the core purpose of this document. So we only present the function signature for one example of what such functions could look like, and leave some comments in the otherwise empty function bodies ๐Ÿงฉ

This sample code is written in C, using the GSS-API C-bindings [RFC2744]. It uses the macro GSS_ERROR() to help unpack the different sorts of information that can be stored in the major status field. Supplementary information doesn't necessarily mean there's an error, so be careful there ๐Ÿ‘€ Applications written in other languages will need to make sure their checks against the major status value are written correctly.

This sample code should be compilable as a standalone program, linked against a GSS-API library. Besides supplying implementations for the token transmission and receipt routines, the initiator needs to specify an explicit target name for the acceptor, so the program runs successfully when linked against most GSS-API libraries ๐ŸŽฏ That target name must match the credentials available to the acceptor. A skeleton for how to do this is provided, using a dummy name.

This sample code assumes use of v2 of the GSS-API. Applications that want to stay compatible with v1 of the GSS-API may need to do additional checks in some places โœจ

5.1. GSS Application Sample Code

#include <unistd.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gssapi/gssapi.h>

/*
 * This helper is used only on buffers that we allocate ourselves (e.g.,
 * from receive_token()).  Buffers allocated by GSS routines must use
 * gss_release_buffer().
 */
static void
release_buffer(gss_buffer_t buf)
{
    free(buf->value);
    buf->value = NULL;
    buf->length = 0;
}

/*
 * Helper to send a token on the specified file descriptor.
 *
 * If errors are encountered, this routine must not directly cause
 * termination of the process because compliant GSS applications
 * must release resources allocated by the GSS library before
 * exiting.
 *
 * Returns 0 on success, nonzero on failure.
 */
static int
send_token(int fd, gss_buffer_t token)
{
    /*
     * Supply token framing and transmission code here.
     *
     * It is advisable for the application protocol to specify the
     * length of the token being transmitted unless the underlying
     * transit does so implicitly.
     *
     * In addition to checking for error returns from whichever
     * syscall(s) are used to send data, applications should have
     * a loop to handle EINTR returns.
     */
    return 1;
}
/*
 * Helper to receive a token on the specified file descriptor.
 *
 * If errors are encountered, this routine must not directly cause
 * termination of the process because compliant GSS applications
 * must release resources allocated by the GSS library before
 * exiting.
 *
 * Returns 0 on success, nonzero on failure.
 */
static int
receive_token(int fd, gss_buffer_t token)
{
    /*
     * Supply token framing and transmission code here.
     *
     * In addition to checking for error returns from whichever
     * syscall(s) are used to receive data, applications should have
     * a loop to handle EINTR returns.
     *
     * This routine is assumed to allocate memory for the local copy
     * of the received token, which must be freed with release_buffer().
     */
    return 1;
}

static void
do_initiator(int readfd, int writefd, int anon)
{
    int initiator_established = 0, ret;
    gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
    OM_uint32 major, minor, req_flags, ret_flags;
    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;
    gss_name_t target_name = GSS_C_NO_NAME;

    /* Applications should set target_name to a real value. */
    name_buf.value = "<service>@<hostname.domain>";
    name_buf.length = strlen(name_buf.value);
    major = gss_import_name(&minor, &name_buf,
                            GSS_C_NT_HOSTBASED_SERVICE, &target_name);
    if (GSS_ERROR(major)) {
        warnx(1, "Could not import name\n");
        goto cleanup;
    }
    /* Mutual authentication will require a token from acceptor to
     * initiator and thus a second call to gss_init_sec_context(). */
    req_flags = GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG;
    if (anon)
        req_flags |= GSS_C_ANON_FLAG;

    while (!initiator_established) {
        /* The initiator_cred_handle, mech_type, time_req,
         * input_chan_bindings, actual_mech_type, and time_rec
         * parameters are not needed in many cases.  We pass
         * GSS_C_NO_CREDENTIAL, GSS_C_NO_OID, 0, NULL, NULL, and NULL
         * for them, respectively. */
        major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &ctx,
                                     target_name, GSS_C_NO_OID,
                                     req_flags, 0, NULL, &input_token,
                                     NULL, &output_token, &ret_flags,
                                     NULL);
        /* This was allocated by receive_token() and is no longer
         * needed.  Free it now to avoid leaks if the loop continues. */
        release_buffer(&input_token);
        if (anon) {
            /* Initiators that wish to remain anonymous must check
             * whether their request has been honored before sending
             * each token. */
            if (!(ret_flags & GSS_C_ANON_FLAG)) {
                warnx("Anonymous requested but not available\n");
                goto cleanup;
            }
        }
        /* Always send a token if we are expecting another input token
         * (GSS_S_CONTINUE_NEEDED is set) or if it is nonempty. */
        if ((major & GSS_S_CONTINUE_NEEDED) ||
            output_token.length > 0) {
            ret = send_token(writefd, &output_token);
            if (ret != 0)
                goto cleanup;
        }
        /* Check for errors after sending the token so that we will send
         * error tokens. */
        if (GSS_ERROR(major)) {
            warnx("gss_init_sec_context() error major 0x%x\n", major);
            goto cleanup;
        }
        /* Free the output token's storage; we don't need it anymore.
         * gss_release_buffer() is safe to call on the output buffer
         * from gss_int_sec_context(), even if there is no storage
         * associated with that buffer. */
        (void)gss_release_buffer(&minor, &output_token);
        if (major & GSS_S_CONTINUE_NEEDED) {
            ret = receive_token(readfd, &input_token);
            if (ret != 0)
                goto cleanup;
        } else if (major == GSS_S_COMPLETE) {
            initiator_established = 1;
        } else {
            /* This situation is forbidden by RFC 2743.  Bail out. */
            warnx("major not complete or continue but not error\n");
            goto cleanup;
        }
    }   /* while (!initiator_established) */
    if ((ret_flags & req_flags) != req_flags) {
        warnx("Negotiated context does not support requested flags\n");
        goto cleanup;
    }
    printf("Initiator's context negotiation successful\n");
cleanup:
    /* We are required to release storage for nonzero-length output
     * tokens.  gss_release_buffer() zeros the length, so we
     * will not attempt to release the same buffer twice. */
    if (output_token.length > 0)
        (void)gss_release_buffer(&minor, &output_token);
    /* Do not request a context deletion token; pass NULL. */
    (void)gss_delete_sec_context(&minor, &ctx, NULL);
    (void)gss_release_name(&minor, &target_name);
}

Okay, bestie, that's the whole initiator loop ๐Ÿ’…โœจ It keeps asking gss_init_sec_context() for the next step, sending any token that's needed, and only calling it done once the major status comes back as GSS_S_COMPLETE. Lots of cleanup happens at the bottom, so nothing leaks ๐Ÿงš

/*
 * Perform authorization checks on the initiator's GSS name object.
 *
 * Returns 0 on success (the initiator is authorized) and nonzero
 * when the initiator is not authorized.
 */
static int
check_authz(gss_name_t client_name)
{
    /*
     * Supply authorization checking code here.
     *
     * Options include bitwise comparison of the exported name against
     * a local database and introspection against name attributes.
     */
    return 0;
}

And this is the authorization hook ๐Ÿ” It's just a placeholder that returns 0, and the comment says you can plug in your own checks, like comparing the exported name against a local database or looking at name attributes. Very main character energy for your own policy logic ๐Ÿฆ‹

static void
do_acceptor(int readfd, int writefd)
{
    int acceptor_established = 0, ret;
    gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
    OM_uint32 major, minor, ret_flags;
    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
    gss_name_t client_name;

    major = GSS_S_CONTINUE_NEEDED;

    while (!acceptor_established) {
        if (major & GSS_S_CONTINUE_NEEDED) {
            ret = receive_token(readfd, &input_token);
            if (ret != 0)
                goto cleanup;
        } else if (major == GSS_S_COMPLETE) {
            acceptor_established = 1;
            break;
        } else {
            /* This situation is forbidden by RFC 2743.  Bail out. */
            warnx("major not complete or continue but not error\n");
            goto cleanup;
        }
        /* We can use the default behavior or do not need the returned
         * information for the parameters acceptor_cred_handle,
         * input_chan_bindings, mech_type, time_rec, and
         * delegated_cred_handle, and pass the values
         * GSS_C_NO_CREDENTIAL, NULL, NULL, NULL, and NULL,
         * respectively.  In some cases the src_name will not be
         * needed, but most likely it will be needed for some
         * authorization or logging functionality. */
        major = gss_accept_sec_context(&minor, &ctx,
                                       GSS_C_NO_CREDENTIAL,
                                       &input_token, NULL,
                                       &client_name, NULL,
                                       &output_token, &ret_flags, NULL,
                                       NULL);
        /* This was allocated by receive_token() and is no longer
         * needed.  Free it now to avoid leaks if the loop continues. */
        release_buffer(&input_token);
        /* Always send a token if we are expecting another input token
         * (GSS_S_CONTINUE_NEEDED is set) or if it is nonempty. */
        if ((major & GSS_S_CONTINUE_NEEDED) ||
            output_token.length > 0) {
            ret = send_token(writefd, &output_token);
            if (ret != 0)

                goto cleanup;
        }
        /* Check for errors after sending the token so that we will send
         * error tokens. */
        if (GSS_ERROR(major)) {
            warnx("gss_accept_sec_context() error major 0x%x\n", major);
            goto cleanup;
        }
        /* Free the output token's storage; we don't need it anymore.
         * gss_release_buffer() is safe to call on the output buffer
         * from gss_accept_sec_context(), even if there is no storage
         * associated with that buffer. */
        (void)gss_release_buffer(&minor, &output_token);
    }   /* while (!acceptor_established) */
    if (!(ret_flags & GSS_C_INTEG_FLAG)) {
        warnx("Negotiated context does not support integrity\n");
        goto cleanup;
    }
    printf("Acceptor's context negotiation successful\n");
    ret = check_authz(client_name);
    if (ret != 0)
        printf("Client is not authorized; rejecting access\n");
cleanup:
    release_buffer(&input_token);
    /* We are required to release storage for nonzero-length output
     * tokens.  gss_release_buffer() zeros the length, so we
     * will not attempt to release the same buffer twice. */
    if (output_token.length > 0)
        (void)gss_release_buffer(&minor, &output_token);
    /* Do not request a context deletion token, pass NULL. */
    (void)gss_delete_sec_context(&minor, &ctx, NULL);
    (void)gss_release_name(&minor, &client_name);
}

The acceptor is the mirror image ๐Ÿชž It waits for tokens first, feeds them into gss_accept_sec_context(), and sends back whatever comes out. It also checks that integrity was negotiated and then runs check_authz() on the client, so the client gets rejected if they're not authorized ๐Ÿ™…โ€โ™€๏ธ

int
main(void)
{
    pid_t pid;
    int fd1 = -1, fd2 = -1;

    /* Create file descriptors for reading/writing here. */
    pid = fork();
    if (pid == 0)
        do_initiator(fd1, fd2, 0);
    else if (pid > 0)
        do_acceptor(fd2, fd1);
    else
        err(1, "fork() failed\n");

    exit(0);
}

And main() just forks, so the child runs the initiator and the parent runs the acceptor ๐Ÿงšโ€โ™€๏ธ๐ŸŒธ

6. Security Considerations โš ๏ธ๐Ÿ’œ

Okay, this part is serious, so I'm turning my whole brain on for it ๐Ÿง ๐Ÿ”ฅ. This document gives a (reasonably) concise description and example of building the GSS-API security context negotiation loop. Basically everything about making and using a GSS security context is security-related, so there are security things sprinkled all through the doc. Still, a few things are worth calling out here ๐Ÿซถ

The GSS-API uses a request-and-check model for features. โœจ An application asks for certain features (like confidentiality protection for messages, or anonymity), but asking for something doesn't mean the GSS implementation has to actually give it to you ๐Ÿ™ˆ. The application has to look at the returned flags to see whether each requested feature is really there. If a feature was non-optional for the application, the application must generate an error. Put differently, the GSS-API will not generate an error if it can't satisfy the features you asked for. So you have to check, bestie ๐Ÿ’€

In lots of cases, it's convenient for GSS acceptors to accept security contexts using multiple acceptor names. For example, that happens when you use the default credential set, as you do when GSS_C_NO_CREDENTIAL is passed to GSS_Accept_sec_context(). That lets an acceptor use any credentials it has access to for accepting contexts, which might not be what a given application actually wants ๐Ÿ˜ฌ. (For example, the Secure Shell daemon (sshd) might want to accept only GSS_C_NT_HOSTBASED credentials of the form host@<hostname>, and not nfs@<hostname>.) Acceptor applications can check which target name the initiator used, but the details are out of scope for this document. See Sections 2.2.6 and 1.1.5 of [RFC2743] ๐Ÿ“š

The C sample code uses the macro GSS_ERROR() to assess the return value of gss_init_sec_context() and gss_accept_sec_context(). This is done to show where checks are needed when you write code in other languages, and what those checks might look like ๐Ÿฆ‹. The C code could be made simpler by leaving that macro out. In applications expecting to receive protected octet streams, this macro should not be used on the result of per-message operations, because it skips checking for supplementary status values such as GSS_S_DUPLICATE_TOKEN, GSS_S_OLD_TOKEN, and so on.

Using the GSS_ERROR() macro on the results of GSS-API per-message operations has resulted in security vulnerabilities in existing software. ๐Ÿšจ๐Ÿ˜ญ Don't be that app, okay?

The security considerations from RFCs 2743 and 2744 remain applicable to consumers of this document ๐Ÿ‘‘.

7. References ๐Ÿ“–

7.1. Normative References ๐ŸŽ€

7.2. Informative References ๐Ÿฆ‹

okay so this is the bit where the RFC shows its receipts ๐Ÿ’… these are the extra reads that help explain the whole thing, and some of them are literally the reason this doc exists ๐Ÿงš


Acknowledgements ๐Ÿซถ

okay the shoutout section!! ๐Ÿฅน huge thanks to Nico Williams and Jeff Hutzleman for basically nudging the author to finally write this down. no cap, this doc would not exist without them ๐Ÿ’–

Author's Address ๐Ÿ’…

Benjamin Kaduk MIT Kerberos Consortium

EMail: kaduk@mit.edu

that's the whole RFC, bestie ๐ŸŽ€โœจ

๐Ÿ“ฆ where this page comes from