Skip to content

Commit

Permalink
Added support to read and write krb5cc files (#176)
Browse files Browse the repository at this point in the history
* Added support to read and write krb5cc files

* Update Kerberos.NET/Replay/TicketCacheBase.cs

Co-authored-by: Günther Foidl <[email protected]>

* Cleanup based on PR comments

* Typo

* Revert

Co-authored-by: Günther Foidl <[email protected]>
  • Loading branch information
SteveSyfuhs and gfoidl authored Aug 9, 2020
1 parent 0f82f36 commit 8822b22
Show file tree
Hide file tree
Showing 17 changed files with 1,134 additions and 141 deletions.
112 changes: 66 additions & 46 deletions Kerberos.NET/Client/KerberosClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public TimeSpan ReceiveTimeout
/// Indicates whether the client should cache service tickets.
/// Ticket-Granting-Tickets are always cached.
/// </summary>
public bool CacheServiceTickets { get; set; }
public bool CacheServiceTickets { get; set; } = true;

/// <summary>
/// The Kerberos options used during the AS-REQ flow.
Expand Down Expand Up @@ -258,57 +258,74 @@ public async Task<ApplicationSessionContext> GetServiceTicket(

using (logger.BeginRequestScope(ScopeId))
{
KrbEncTgsRepPart encKdcRepPart;
KrbEncTgsRepPart encKdcRepPart = null;
KrbEncryptionKey sessionKey;
KerberosClientCacheEntry serviceTicketCacheEntry;

do
if (rst.KdcOptions == 0)
{
cancellation.ThrowIfCancellationRequested();
rst.KdcOptions = KdcOptions;
}

if (rst.KdcOptions == 0)
{
rst.KdcOptions = KdcOptions;
}
if (rst.UserToUserTicket != null)
{
rst.KdcOptions |= KdcOptions.EncTktInSkey;
}

if (rst.UserToUserTicket != null)
{
rst.KdcOptions |= KdcOptions.EncTktInSkey;
}
if (!string.IsNullOrWhiteSpace(rst.S4uTarget))
{
rst.KdcOptions |= KdcOptions.Forwardable;
}

if (!string.IsNullOrWhiteSpace(rst.S4uTarget))
{
rst.KdcOptions |= KdcOptions.Forwardable;
}
do
{
cancellation.ThrowIfCancellationRequested();

// first of all, do we already have the ticket?

serviceTicketCacheEntry = await Cache.Get<KerberosClientCacheEntry>(
serviceTicketCacheEntry = Cache.GetCacheItem<KerberosClientCacheEntry>(
originalServicePrincipalName.FullyQualifiedName,
rst.S4uTarget
);

bool cacheResult = false;

if (serviceTicketCacheEntry.KdcResponse == null || !CacheServiceTickets)
{
// nope, try and request it from the KDC that issued the TGT

var tgtEntry = await CopyTicket(tgtCacheName);
var tgtEntry = CopyTicket(tgtCacheName);

rst.Realm = ResolveKdcTarget(tgtEntry);

serviceTicketCacheEntry = await RequestTgs(rst, tgtEntry, cancellation);

cacheResult = true;
}

// we got a ticket of some sort from the KDC
// we got a ticket of some sort from the cache or the KDC

encKdcRepPart = serviceTicketCacheEntry.KdcResponse.EncPart.Decrypt(
serviceTicketCacheEntry.SessionKey.AsKey(),
serviceTicketCacheEntry.SessionKey.Usage,
d => KrbEncTgsRepPart.DecodeApplication(d)
);
KrbPrincipalName respondedSName;

VerifyNonces(serviceTicketCacheEntry.Nonce, encKdcRepPart.Nonce);
if (serviceTicketCacheEntry.KdcResponse?.EncPart?.Cipher.Length > 0)
{
encKdcRepPart = serviceTicketCacheEntry.KdcResponse.EncPart.Decrypt(
serviceTicketCacheEntry.SessionKey.AsKey(),
serviceTicketCacheEntry.SessionKey.Usage,
d => KrbEncTgsRepPart.DecodeApplication(d)
);

VerifyNonces(serviceTicketCacheEntry.Nonce, encKdcRepPart.Nonce);

var respondedSName = encKdcRepPart.SName;
sessionKey = encKdcRepPart.Key;
respondedSName = encKdcRepPart.SName;
}
else
{
// cache shortcut when using krb5cc
sessionKey = serviceTicketCacheEntry.SessionKey;
respondedSName = serviceTicketCacheEntry.SName;
}

receivedRequestedTicket = false;

Expand All @@ -328,7 +345,7 @@ public async Task<ApplicationSessionContext> GetServiceTicket(

string referral = TryFindReferralShortcut(encKdcRepPart);

if(string.IsNullOrWhiteSpace(referral))
if (string.IsNullOrWhiteSpace(referral))
{
referral = originalServicePrincipalName.FullyQualifiedName;
}
Expand All @@ -347,17 +364,20 @@ public async Task<ApplicationSessionContext> GetServiceTicket(
receivedRequestedTicket = true;
}

// regardless of what state we're in we got a valuable ticket
// that can be used in future requests

await Cache.Add(new TicketCacheEntry
if (cacheResult)
{
Key = respondedSName.FullyQualifiedName,
Container = rst.S4uTarget,
RenewUntil = encKdcRepPart.RenewTill,
Expires = encKdcRepPart.EndTime,
Value = serviceTicketCacheEntry
});
// regardless of what state we're in we got a valuable ticket
// that can be used in future requests

Cache.Add(new TicketCacheEntry
{
Key = respondedSName.FullyQualifiedName,
Container = rst.S4uTarget,
RenewUntil = encKdcRepPart.RenewTill,
Expires = encKdcRepPart.EndTime,
Value = serviceTicketCacheEntry
});
}

// if we didn't receive the ticket we requested but got a referral
// we need to kick off a new request
Expand All @@ -370,11 +390,11 @@ await Cache.Add(new TicketCacheEntry
{
ApReq = KrbApReq.CreateApReq(
serviceTicketCacheEntry.KdcResponse,
encKdcRepPart.Key.AsKey(),
sessionKey.AsKey(),
rst.ApOptions,
out KrbAuthenticator authenticator
),
SessionKey = authenticator.Subkey ?? encKdcRepPart.Key,
SessionKey = authenticator.Subkey ?? sessionKey,
CTime = authenticator.CTime,
CuSec = authenticator.CuSec,
SequenceNumber = authenticator.SequenceNumber
Expand Down Expand Up @@ -544,9 +564,9 @@ CancellationToken cancellation
return entry;
}

private async Task<KerberosClientCacheEntry> CopyTicket(string spn)
private KerberosClientCacheEntry CopyTicket(string spn)
{
var entry = await Cache.Get<KerberosClientCacheEntry>(spn);
var entry = Cache.GetCacheItem<KerberosClientCacheEntry>(spn);

lock (_syncTicketCache)
{
Expand Down Expand Up @@ -578,7 +598,7 @@ public async Task RenewTicket(string spn = null)
spn = $"krbtgt/{DefaultDomain}";
}

var entry = await CopyTicket(spn);
var entry = CopyTicket(spn);

var tgs = KrbTgsReq.CreateTgsReq(
new RequestServiceTicket
Expand All @@ -605,16 +625,16 @@ out KrbEncryptionKey subkey
d => KrbEncTgsRepPart.DecodeApplication(d)
);

await CacheTgt(tgsRep, encKdcRepPart);
CacheTgt(tgsRep, encKdcRepPart);
}

private async Task CacheTgt(KrbKdcRep kdcRep, KrbEncKdcRepPart encKdcRepPart)
private void CacheTgt(KrbKdcRep kdcRep, KrbEncKdcRepPart encKdcRepPart)
{
var key = kdcRep.Ticket.SName.FullyQualifiedName;

encKdcRepPart.Key.Usage = KeyUsage.EncTgsRepPartSessionKey;

await Cache.Add(new TicketCacheEntry
Cache.Add(new TicketCacheEntry
{
Key = key,
Expires = encKdcRepPart.EndTime,
Expand Down Expand Up @@ -652,7 +672,7 @@ private async Task RequestTgt(KerberosCredential credential)

DefaultDomain = credential.Domain;

await CacheTgt(asRep, decrypted);
CacheTgt(asRep, decrypted);
}

private KrbEncKdcRepPart DecodeEncKdcRepPart<T>(ReadOnlyMemory<byte> decrypted)
Expand Down
2 changes: 2 additions & 0 deletions Kerberos.NET/Client/KerberosClientCacheEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public struct KerberosClientCacheEntry
public KrbKdcRep KdcResponse;

public int Nonce { get; set; }

public KrbPrincipalName SName { get; set; }
}
}
Loading

0 comments on commit 8822b22

Please sign in to comment.