Skip to content

Add GSS channel binding support per RFC 4121 section 4.1.1.2 for AP-REQ and TGS-REQ#422

Merged
SteveSyfuhs merged 8 commits intodotnet:developfrom
thadumi:cbt-validation
Mar 10, 2026
Merged

Add GSS channel binding support per RFC 4121 section 4.1.1.2 for AP-REQ and TGS-REQ#422
SteveSyfuhs merged 8 commits intodotnet:developfrom
thadumi:cbt-validation

Conversation

@thadumi
Copy link

@thadumi thadumi commented Feb 20, 2026

What's the problem?

TLS Channel Binding Tokens solve authentication relay attacks (MITM).

Without CBT attacker intercepts a client's connection, terminates the TLS session, and establishes a separate TLS session to the real server. The attacker then relays the client's Kerberos AP-REQ (which is valid and properly encrypted) to the server. The server accepts it because the Kerberos ticket itself is perfectly legitimate and it has no knowledge that it arrived through an intermediary.

RFC 4121 describes Channel Binding Tokens for Kerberos.

  • Bugfix
  • New Feature

What's the solution?

CBT ties the Kerberos authentication to the specific TLS channel it was intended for:

  • the client hashes the TLS server certificate (or other channel-unique material) into the application_data of the gss_channel_bindings_struct, computes the MD5 Bnd hash, and embeds it inside the authenticator checksum of the AP-REQ (encrypted, so the attacker can't modify it).
  • the server independently computes the same hash from its own TLS certificate and compares it against the hash in the AP-REQ.
    If an attacker is relaying, the two TLS sessions have different certificates, the attacker's certificate on the client side vs. the real server's certificate on the server side. The hashes won't match, and the server rejects the request.

As such we are introducing:

  • a new property DecryptedKrbApReq.ExpectedChannelBindings to allow the server to specify the expected Channel Binding information in the AP-REQ
  • a new property KerberosValidator.ExpectedChannelBindings which is symmetrical to DecryptedKrbApReq to facilitate the usage of this feature
  • a new validation flag ValidationActions.ChannelBinding that triggers ValidateChannelBinding() during Validate()
    ValidateChannelBinding() uses the property DecryptedKrbApReq.ExpectedChannelBindings to ensure that the unique material present in the AP-REQ.

On server side the intended usage is

// Option 1: Using KerberosValidator 
var validator = new KerberosValidator(serviceKey)
{
    ExpectedChannelBindings = new GssChannelBindings
    {
        ApplicationData = tlsServerEndpointBinding
    }
};

var identity = await validator.Validate(apReqBytes);

// Option 2: Using DecryptedKrbApReq 
var decrypted = new DecryptedKrbApReq(apReq);
decrypted.Decrypt(serviceKey);

decrypted.ExpectedChannelBindings = new GssChannelBindings
{
    ApplicationData = tlsServerEndpointBinding
};

decrypted.Validate(ValidationActions.All);
// Throws KerberosValidationException if bindings don't match

On client side

// Build the TLS channel binding from the server's certificate hash
var channelBindings = new GssChannelBindings
{
    ApplicationData = client.GetTlsServerEndpointBinding()
};

var rst = new RequestServiceTicket
{
    ServicePrincipalName = "host/server.example.com",
    GssContextFlags = GssContextEstablishmentFlag.GSS_C_MUTUAL_FLAG,
    ChannelBindings = channelBindings
};

var apReq = await client.GetServiceTicket(rst);

As of this PR, Channel Binding validation is enabled by default and follow an opt‑out model. Validation is bypassed only when ExpectedChannelBindings is unset, or when Channel Bindings are explicitly excluded through flags: decrypted.Validate(ValidationActions.All & ~ValidationActions.ChannelBinding);

Describe the solution here.

  • Includes unit tests
  • Requires manual test

@thadumi
Copy link
Author

thadumi commented Feb 20, 2026

@dotnet-policy-service agree company="Microsoft"

@thadumi thadumi marked this pull request as ready for review February 20, 2026 15:47
@thadumi thadumi changed the title Add GSS channel binding support per RFC 4121 section 4.1.1.2 for AP-REQ Add GSS channel binding support per RFC 4121 section 4.1.1.2 for AP-REQ and TGS-REQ Feb 20, 2026
/// Accepts a raw SEC_CHANNEL_BINDINGS buffer
/// and converts it to <see cref="ExpectedChannelBindings"/>.
/// </summary>
public ReadOnlyMemory<byte> ExpectedRawChannelBindings
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I feel like intention might be clearer with a setter method instead of a setter-only field. Or you could just make the caller call GssChannelBindings.FromSecChannelBindings. But no strong opinion on this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, should be a function at a minimum

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to SetExpectedChannelBindingsFromSecChannelBindings

/// Convenience property that accepts a raw SEC_CHANNEL_BINDINGS buffer (as returned by Windows SSPI)
/// and converts it to <see cref="ExpectedChannelBindings"/>.
/// </summary>
public ReadOnlyMemory<byte> ExpectedRawChannelBindings
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converted it to SetExpectedChannelBindingsFromSecChannelBindings


var delegationInfo = checksum.DecodeDelegation();

this.ChannelBindingHash = delegationInfo.ChannelBinding;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably needs a null check: this.ChannelBindingHash = delegationInfo?.ChannelBinding;

/// Accepts a raw SEC_CHANNEL_BINDINGS buffer
/// and converts it to <see cref="ExpectedChannelBindings"/>.
/// </summary>
public ReadOnlyMemory<byte> ExpectedRawChannelBindings
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, should be a function at a minimum


protected KdcServerOptions Options { get; }

/// <summary>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this can go away entirely. KDC has no concept of channel bindings.

Copy link
Author

@thadumi thadumi Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed completely from both KDC ans PreAuthContext, as the CBT property in PreAuthContext was added for KDC leg only

@SteveSyfuhs SteveSyfuhs merged commit f41e7df into dotnet:develop Mar 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants