-
Notifications
You must be signed in to change notification settings - Fork 106
Add GSS channel binding support per RFC 4121 section 4.1.1.2 for AP-REQ and TGS-REQ #422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
f610c23
b37441a
7302181
c102ce1
1e48486
44d78f3
f0d7194
01c3908
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,28 @@ public DecryptedKrbApReq(KrbApReq token, MessageType incomingMessageType = Messa | |
|
|
||
| public KrbEncKrbCredPart DelegationTicket { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// The channel binding hash (Bnd field) extracted from the authenticator checksum, | ||
| /// as described in RFC 4121 section 4.1.1.2. This is a 16-byte MD5 hash of the | ||
| /// <see cref="GssChannelBindings"/> structure. Will be null or empty if no channel | ||
| /// bindings were supplied by the initiator. | ||
| /// </summary> | ||
| public ReadOnlyMemory<byte> ChannelBindingHash { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Expected channel bindings to validate against when <see cref="ValidationActions.ChannelBinding"/> is enabled. | ||
| /// </summary> | ||
| public GssChannelBindings ExpectedChannelBindings { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// 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 | ||
| { | ||
| set { this.ExpectedChannelBindings = GssChannelBindings.FromSecChannelBindings(value); } | ||
| } | ||
|
|
||
| public KerberosKey SessionKey { get; private set; } | ||
|
|
||
| private readonly KrbApReq token; | ||
|
|
@@ -161,6 +183,8 @@ private KrbEncKrbCredPart TryExtractDelegationTicket(KrbChecksum checksum) | |
|
|
||
| var delegationInfo = checksum.DecodeDelegation(); | ||
|
|
||
| this.ChannelBindingHash = delegationInfo.ChannelBinding; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably needs a null check: |
||
|
|
||
| var delegation = delegationInfo?.DelegationTicket; | ||
|
|
||
| if (delegation == null) | ||
|
|
@@ -212,6 +236,35 @@ public override void Validate(ValidationActions validation) | |
| { | ||
| this.ValidateTicketRenewal(this.Ticket.RenewTill, now, this.Skew); | ||
| } | ||
|
|
||
| if (validation.HasFlag(ValidationActions.ChannelBinding)) | ||
| { | ||
| this.ValidateChannelBinding(); | ||
| } | ||
| } | ||
|
|
||
| protected virtual void ValidateChannelBinding() | ||
| { | ||
| if (this.ExpectedChannelBindings == null) | ||
|
thadumi marked this conversation as resolved.
|
||
| { | ||
| return; | ||
| } | ||
|
|
||
| var expectedHash = this.ExpectedChannelBindings.ComputeBindingHash(); | ||
|
|
||
| if (this.ChannelBindingHash.Length == 0) | ||
| { | ||
| throw new KerberosValidationException( | ||
| "Channel Bindings are required by the acceptor but were not supplied by the initiator." | ||
| ); | ||
| } | ||
|
|
||
| if (!KerberosCryptoTransformer.AreEqualSlow(expectedHash.Span, this.ChannelBindingHash.Span)) | ||
| { | ||
| throw new KerberosValidationException( | ||
| "The Channel Bindings hash from the initiator does not match the expected channel bindings." | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| public override string ToString() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| // ----------------------------------------------------------------------- | ||
| // Licensed to The .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // ----------------------------------------------------------------------- | ||
|
|
||
| using System; | ||
| using System.Buffers.Binary; | ||
| using System.IO; | ||
| using System.Security.Cryptography; | ||
|
|
||
| namespace Kerberos.NET.Entities | ||
| { | ||
| /// <summary> | ||
| /// Represents gss_channel_bindings_struct per RFC 4121 section 4.1.1.2. | ||
| /// </summary> | ||
| public class GssChannelBindings | ||
| { | ||
| private const int SecChannelBindingsHeaderSize = 32; | ||
|
|
||
| public int InitiatorAddrType { get; set; } | ||
|
|
||
| public ReadOnlyMemory<byte> InitiatorAddress { get; set; } | ||
|
|
||
| public int AcceptorAddrType { get; set; } | ||
|
|
||
| public ReadOnlyMemory<byte> AcceptorAddress { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Protocol-specific channel binding data | ||
| /// e.g. tls-server-end-point or tls-unique as per RFC 5929 | ||
| /// </summary> | ||
| public ReadOnlyMemory<byte> ApplicationData { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Computes the 16-byte MD5 binding hash (Bnd field) per RFC 4121 section 4.1.1.2. | ||
| /// </summary> | ||
| public ReadOnlyMemory<byte> ComputeBindingHash() | ||
| { | ||
| using var stream = new MemoryStream(); | ||
| using var writer = new BinaryWriter(stream); | ||
|
|
||
| writer.Write(this.InitiatorAddrType); | ||
| writer.Write(this.InitiatorAddress.Length); | ||
|
|
||
| if (this.InitiatorAddress.Length > 0) | ||
| { | ||
| writer.Write(this.InitiatorAddress.ToArray()); | ||
| } | ||
|
|
||
| writer.Write(this.AcceptorAddrType); | ||
| writer.Write(this.AcceptorAddress.Length); | ||
|
|
||
| if (this.AcceptorAddress.Length > 0) | ||
| { | ||
| writer.Write(this.AcceptorAddress.ToArray()); | ||
| } | ||
|
|
||
| writer.Write(this.ApplicationData.Length); | ||
|
|
||
| if (this.ApplicationData.Length > 0) | ||
| { | ||
| writer.Write(this.ApplicationData.ToArray()); | ||
| } | ||
|
|
||
| var data = stream.ToArray(); | ||
|
|
||
| using var md5 = MD5.Create(); | ||
| return md5.ComputeHash(data); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Parses a raw SEC_CHANNEL_BINDINGS flat buffer (as returned by Windows SSPI) into a <see cref="GssChannelBindings"/>. | ||
| /// </summary> | ||
| public static GssChannelBindings FromSecChannelBindings(ReadOnlyMemory<byte> rawBuffer) | ||
| { | ||
| if (rawBuffer.Length < SecChannelBindingsHeaderSize) | ||
| { | ||
| throw new ArgumentException( | ||
| $"Buffer is too small to contain a SEC_CHANNEL_BINDINGS header. Expected at least {SecChannelBindingsHeaderSize} bytes.", | ||
| nameof(rawBuffer)); | ||
| } | ||
|
|
||
| var span = rawBuffer.Span; | ||
|
|
||
| var bindings = new GssChannelBindings | ||
| { | ||
| InitiatorAddrType = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(0)), | ||
| }; | ||
|
|
||
| int initiatorLength = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(4)); | ||
| int initiatorOffset = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(8)); | ||
|
|
||
| bindings.AcceptorAddrType = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(12)); | ||
|
|
||
| int acceptorLength = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(16)); | ||
| int acceptorOffset = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(20)); | ||
|
|
||
| int applicationDataLength = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(24)); | ||
| int applicationDataOffset = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(28)); | ||
|
|
||
| if (initiatorLength > 0) | ||
| { | ||
| bindings.InitiatorAddress = rawBuffer.Slice(initiatorOffset, initiatorLength); | ||
| } | ||
|
|
||
| if (acceptorLength > 0) | ||
| { | ||
| bindings.AcceptorAddress = rawBuffer.Slice(acceptorOffset, acceptorLength); | ||
| } | ||
|
|
||
| if (applicationDataLength > 0) | ||
| { | ||
| bindings.ApplicationData = rawBuffer.Slice(applicationDataOffset, applicationDataLength); | ||
| } | ||
|
|
||
| return bindings; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,20 @@ public abstract class KdcMessageHandlerBase | |
|
|
||
| protected KdcServerOptions Options { get; } | ||
|
|
||
| /// <summary> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| /// Expected channel bindings for this request's TGS-REQ validation. | ||
| /// </summary> | ||
| public GssChannelBindings ExpectedChannelBindings { get; set; } | ||
|
thadumi marked this conversation as resolved.
Outdated
|
||
|
|
||
| /// <summary> | ||
| /// Accepts a raw SEC_CHANNEL_BINDINGS buffer | ||
| /// and converts it to <see cref="ExpectedChannelBindings"/>. | ||
| /// </summary> | ||
| public ReadOnlyMemory<byte> ExpectedRawChannelBindings | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, should be a function at a minimum
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated to SetExpectedChannelBindingsFromSecChannelBindings |
||
| { | ||
| set { this.ExpectedChannelBindings = GssChannelBindings.FromSecChannelBindings(value); } | ||
| } | ||
|
|
||
| protected IRealmService RealmService { get; private set; } | ||
|
|
||
| public IDictionary<PaDataType, PreAuthHandlerConstructor> PreAuthHandlers => this.preAuthHandlers; | ||
|
|
@@ -103,7 +117,10 @@ public virtual async Task<ReadOnlyMemory<byte>> ExecuteAsync() | |
| { | ||
| try | ||
| { | ||
| var context = new PreAuthenticationContext(); | ||
| var context = new PreAuthenticationContext | ||
| { | ||
| ExpectedChannelBindings = this.ExpectedChannelBindings | ||
| }; | ||
|
|
||
| this.DecodeMessage(context); | ||
|
|
||
|
|
@@ -127,7 +144,10 @@ public virtual ReadOnlyMemory<byte> Execute() | |
| { | ||
| try | ||
| { | ||
| var context = new PreAuthenticationContext(); | ||
| var context = new PreAuthenticationContext | ||
| { | ||
| ExpectedChannelBindings = this.ExpectedChannelBindings | ||
| }; | ||
|
|
||
| this.DecodeMessage(context); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converted it to SetExpectedChannelBindingsFromSecChannelBindings