Hi there,

I have a problem of which I am not sure if the solution is trivial
or very complex. Anyway, here are the details and the code I tried
to do this with...

ENVIRONMENT

- GINA Stub DLL (i.e. running as part of winlogon.exe process)
- Windows XP (SP2)
- Certificate based logon (smart card / CSP)

ORIGINAL PROBLEM

- Certificate Unlock via smart card/CSP takes a long time (15 min),
which is too long for user acceptance at a customer.

TARGET SOLUTION

- Unlock workstation directly, without passing via MS GINA and CSP,
by checking if the smart card has the same private RSA key
(determined
by signing a test pattern at logon and at unlock and comparing
them).
- This is much faster.
- It is acceptable for the customer that not at each unlock revokation
lists are consulted etc.
- But one thing is a problem: Kerberos tickets are not touched in this
case (normally at unlock, they are apparently all renewed). This is
a problem for running applications.

APPROACH TO PROLONG KERBEROS TICKETS

- Get tickets by calling LsaCallAuthenticationPackage (several calls,
first KERB_QUERY_TKT_CACHE_REQUEST to get a list of all tickets in
the cache, then twice with KERB_RETRIEVE_TKT_REQUEST, to get the
ticket granting ticket (ASN.1 encoded buffer) with forwarded flag
set and the "host/" ticket (ASN.1 encoded buffer).
- Call LsaLogonUser with KERB_TICKET_UNLOCK_LOGON, in the hope that if
this function succeeds, all tickets will be renewed.

QUESTIONS

- I could not get this approach to work: LsaLogonUser returns with
status 0 (OK) but with protocolStatus STATUS_INSUFFICIENT_RESOURCES
(0xc000009a). What am I doing wrong ?
- Are there other, more elegant solutions to the problem of renewing
Kerberos tickets under Windows XP ?
- Are there maybe more elegant solutions to the original problem ?

Thanks in advance to any hints that might help here.

--


//
************************************************** ****************************
// Globals, set initially in WlxLoggedOutSAS() when logging on.
//
************************************************** ****************************

extern LUID gLUID;
extern HANDLE ghUserToken;

//
************************************************** ****************************
// Utility function to set LSA_STRING structure from given ANSI
string.
//
************************************************** ****************************

void setLsaString (LSA_STRING* lsaString, const char* ansiString)
{
lsaString->Length = (USHORT)(strlen(ansiString) *
sizeof(*ansiString));
lsaString->MaximumLength = (USHORT)(lsaString->Length);
lsaString->Buffer = const_cast(ansiString);
}

//
************************************************** ****************************
// Utility function to set UNICODE_STRING structure from given unicode
string.
//
************************************************** ****************************

void setUnicodeString (UNICODE_STRING* target, wchar_t* source, USHORT
cbMax) {
target->Length = cbMax;
target->MaximumLength = cbMax;
target->Buffer = source;
}

//
************************************************** ****************************
// Renew Kerberos tickets at certificate unlock.
// (Faster unlock without using MS GINA Enter PIN dialog.)
//
************************************************** ****************************

int RenewKerberosTickets ()
{
SECUDE_FNAME(RenewKerberosTickets);

WRITELN "About to renew Kerberos tickets...");

//------------------------------------------------------------------------------
// Get LSA (Local Security Authority) handle.
//------------------------------------------------------------------------------

LSA_STRING logonProcessName;
setLsaString (&logonProcessName, "User32LogonProcess"); //
winlogon
HANDLE hLsa = NULL;
LSA_OPERATIONAL_MODE tLsaOpMode;
NTSTATUS status = LsaRegisterLogonProcess(
&logonProcessName, // [in] PLSA_STRING LogonProcessName
&hLsa, // [out] PHANDLE LsaHandle
&tLsaOpMode // [out] PLSA_OPERATIONAL_MODE SecurityMode
);
if (status) {
WRITELN "LsaRegisterLogonProcess() returned with status 0x
%08x", status);
return status;
}

//------------------------------------------------------------------------------
// Lookup Kerberos authentication package.
//------------------------------------------------------------------------------

LSA_STRING packageName;
setLsaString (&packageName, MICROSOFT_KERBEROS_NAME_A); // string
is simply "Kerberos"
ULONG ulAuthenticationPackage = 0;
status = LsaLookupAuthenticationPackage(
hLsa, // [in] HANDLE LsaHandle
&packageName, // [in] PLSA_STRING PackageName
&ulAuthenticationPackage // [out] PULONG
AuthenticationPackage
);
if (status) {
WRITELN "LsaLookupAuthenticationPackage() returned with status
0x%08x", status);
return status;
}

//------------------------------------------------------------------------------
// Get list of all cached Kerberos tickets for our LUID.
//------------------------------------------------------------------------------

KERB_QUERY_TKT_CACHE_REQUEST kerbQueryTktCacheRequest = {
KerbQueryTicketCacheMessage, // KERB_PROTOCOL_MESSAGE_TYPE
MessageType
gLUID // LUID LogonId
};
PKERB_QUERY_TKT_CACHE_RESPONSE pKerbQueryTktCacheResponse = NULL;
ULONG ulReturnBufferLength = 0;
NTSTATUS protocolStatus = 0;
status = LsaCallAuthenticationPackage(
hLsa, // [in] HANDLE
LsaHandle,
ulAuthenticationPackage, // [in] ULONG
AuthenticationPackage,
(PVOID)&kerbQueryTktCacheRequest, // [in] PVOID
ProtocolSubmitBuffer,
sizeof(kerbQueryTktCacheRequest), // [in] ULONG
SubmitBufferLength,
(PVOID*)&pKerbQueryTktCacheResponse, // [out] PVOID*
ProtocolReturnBuffer,
&ulReturnBufferLength, // [out] PULONG
ReturnBufferLength,
&protocolStatus // [out] PNTSTATUS
ProtocolStatus
);
if (status || protocolStatus) {
WRITELN
"LsaCallAuthenticationPackage(KERB_QUERY_TKT_CACHE_ REQUEST)"
"returned with status 0x%08x and protocol status 0x%08x",
status, protocolStatus);
if (status) return status;
return protocolStatus;
}

//TODO: Release memory for response data.

//NOTE: This gives info about all tickets (see below), but the ASN.
1 representation,
// which is needed for renewing the tickets is missing, yet.

// Structure of returned data:

// typedef struct _KERB_QUERY_TKT_CACHE_RESPONSE {
// KERB_PROTOCOL_MESSAGE_TYPE MessageType;
// ULONG CountOfTickets;
// KERB_TICKET_CACHE_INFO Tickets[ANYSIZE_ARRAY];
// } KERB_QUERY_TKT_CACHE_RESPONSE,
*PKERB_QUERY_TKT_CACHE_RESPONSE;

// typedef struct _KERB_TICKET_CACHE_INFO {
// UNICODE_STRING ServerName;
// UNICODE_STRING RealmName;
// LARGE_INTEGER StartTime;
// LARGE_INTEGER EndTime;
// LARGE_INTEGER RenewTime;
// LONG EncryptionType;
// ULONG TicketFlags;
// } KERB_TICKET_CACHE_INFO, *PKERB_TICKET_CACHE_INFO;

//------------------------------------------------------------------------------
// Get ASN.1 encoded logon ticket ("host/",
"ServiceTicket")
//------------------------------------------------------------------------------

//TODO: Determine index of "host/" ticket from
name and flags.

ULONG ulIndex_host = 5; // !!!! hardcoded !!!!

KERB_RETRIEVE_TKT_REQUEST kerbRetrieveTktRequest_host = {

KerbRetrieveEncodedTicketMessage, //
KERB_PROTOCOL_MESSAGE_TYPE MessageType

gLUID, //
LUID LogonId
pKerbQueryTktCacheResponse-
>Tickets[ulIndex_host].ServerName, // UNICODE_STRING TargetName

pKerbQueryTktCacheResponse-
>Tickets[ulIndex_host].TicketFlags, // ULONG TicketFlags


KERB_RETRIEVE_TICKET_USE_CACHE_ONLY, //
ULONG CacheOptions
pKerbQueryTktCacheResponse-
>Tickets[ulIndex_host].EncryptionType, // LONG EncryptionType

{0,
0} //
SecHandle CredentialsHandle
};

//NOTE: Actual CredentialsHandle would only be needed for
KERB_RETRIEVE_TICKET_USE_CREDHANDLE.

// The following code appends the target name immediately after
the above struct.
// Experimentally, this must often be the case for Lsa*()
functions, even though
// this is usually not documented in MSDN. See also:
// http://www.pluralsight.com/blogs/kei...1/27/5486.aspx
// ("LsaLogonUser - the function from hell")

int iRequestLen_host = sizeof(KERB_RETRIEVE_TKT_REQUEST);
int iTargetNameLen_host =
kerbRetrieveTktRequest_host.TargetName.Length;
int iTotalLen_host = iRequestLen_host + iTargetNameLen_host;
KERB_RETRIEVE_TKT_REQUEST* pKerbRetrieveTktRequest_host =
(KERB_RETRIEVE_TKT_REQUEST*) malloc (iTotalLen_host);
memcpy (pKerbRetrieveTktRequest_host,
&kerbRetrieveTktRequest_host, iRequestLen_host);
void* pTargetNameBuf_host = (unsigned
char*)pKerbRetrieveTktRequest_host + iRequestLen_host;
memcpy (pTargetNameBuf_host,
kerbRetrieveTktRequest_host.TargetName.Buffer, iTargetNameLen_host);
pKerbRetrieveTktRequest_host->TargetName.Buffer =
(PWSTR)pTargetNameBuf_host;

PKERB_RETRIEVE_TKT_RESPONSE pKerbRetrieveTktResponse_host = NULL;
ULONG ulReturnBufferLength_host = 0;
NTSTATUS protocolStatus_host = 0;
status = LsaCallAuthenticationPackage(
hLsa, // [in] HANDLE
LsaHandle,
ulAuthenticationPackage, // [in] ULONG
AuthenticationPackage,
(PVOID)pKerbRetrieveTktRequest_host, // [in] PVOID
ProtocolSubmitBuffer,
iTotalLen_host, // [in] ULONG
SubmitBufferLength,
(PVOID*)&pKerbRetrieveTktResponse_host, // [out] PVOID*
ProtocolReturnBuffer,
&ulReturnBufferLength_host, // [out] PULONG
ReturnBufferLength,
&protocolStatus_host // [out] PNTSTATUS
ProtocolStatus
);
if (status || protocolStatus_host) {
WRITELN
"LsaCallAuthenticationPackage(KERB_RETRIEVE_TKT_REQ UEST, host)"
"returned with status 0x%08x and protocol status 0x%08x",
status, protocolStatus_host);
if (status) return status;
return protocolStatus_host;
}

// Structure of returned data:

// typedef struct _KERB_RETRIEVE_TKT_RESPONSE {
// KERB_EXTERNAL_TICKET Ticket;
// } KERB_RETRIEVE_TKT_RESPONSE, *PKERB_RETRIEVE_TKT_RESPONSE;

// typedef struct _KERB_EXTERNAL_TICKET {
// PKERB_EXTERNAL_NAME ServiceName;
// PKERB_EXTERNAL_NAME TargetName;
// PKERB_EXTERNAL_NAME ClientName;
// UNICODE_STRING DomainName;
// UNICODE_STRING TargetDomainName;
// UNICODE_STRING AltTargetDomainName;
// KERB_CRYPTO_KEY SessionKey;
// ULONG TicketFlags;
// ULONG Flags;
// LARGE_INTEGER KeyExpirationTime;
// LARGE_INTEGER StartTime;
// LARGE_INTEGER EndTime;
// LARGE_INTEGER RenewUntil;
// LARGE_INTEGER TimeSkew;
// ULONG EncodedTicketSize;
// PUCHAR EncodedTicket;
// } KERB_EXTERNAL_TICKET, *PKERB_EXTERNAL_TICKET;

//------------------------------------------------------------------------------
// Get ASN.1 encoded ticket granting ticket ("krbtgt/")
//------------------------------------------------------------------------------

//TODO: Determine index of "krbtgt/" ticket from name and
flags.
//NOTE: There are two, take the one with "forwarded" flag set (see
MSDN).

ULONG ulIndex_krbtgt = 0; // !!!! hardcoded !!!!

KERB_RETRIEVE_TKT_REQUEST kerbRetrieveTktRequest_krbtgt = {

KerbRetrieveEncodedTicketMessage, //
KERB_PROTOCOL_MESSAGE_TYPE MessageType

gLUID, //
LUID LogonId
pKerbQueryTktCacheResponse-
>Tickets[ulIndex_krbtgt].ServerName, // UNICODE_STRING TargetName

pKerbQueryTktCacheResponse-
>Tickets[ulIndex_krbtgt].TicketFlags, // ULONG TicketFlags

0 /
*KERB_RETRIEVE_TICKET_USE_CACHE_ONLY*/, //
ULONG CacheOptions
pKerbQueryTktCacheResponse-
>Tickets[ulIndex_krbtgt].EncryptionType, // LONG EncryptionType

{0,
0} //
SecHandle CredentialsHandle
};

//NOTE: If one sets CacheOptions to
KERB_RETRIEVE_TICKET_USE_CACHE_ONLY, the ticket is not found
// (the one without the forwarded flag set would be found, as
would apparently all the others,
// but not the forwarded ticket granting ticket). In that
case, returns protocol status
// STATUS_OBJECT_NAME_NOT_FOUND (0xc0000034). Works
experimentally if one sets CacheOptions
// to 0, which means (according to MSDN):
// "Set this member to zero to indicate that the cache should
be searched and if no
// ticket if found, a new ticket should be requested. If
this member is not set to zero,
// the returned ticket will not be cached."

//NOTE: Actual CredentialsHandle would only be needed for
KERB_RETRIEVE_TICKET_USE_CREDHANDLE.

// The following code appends the target name immediately after
the above struct.

int iRequestLen_krbtgt = sizeof(KERB_RETRIEVE_TKT_REQUEST);
int iTargetNameLen_krbtgt =
kerbRetrieveTktRequest_krbtgt.TargetName.Length;
int iTotalLen_krbtgt = iRequestLen_krbtgt + iTargetNameLen_krbtgt;
KERB_RETRIEVE_TKT_REQUEST* pKerbRetrieveTktRequest_krbtgt =
(KERB_RETRIEVE_TKT_REQUEST*) malloc (iTotalLen_krbtgt);
memcpy (pKerbRetrieveTktRequest_krbtgt,
&kerbRetrieveTktRequest_krbtgt, iRequestLen_krbtgt);
void* pTargetNameBuf_krbtgt = (unsigned
char*)pKerbRetrieveTktRequest_krbtgt + iRequestLen_krbtgt;
memcpy (pTargetNameBuf_krbtgt,
kerbRetrieveTktRequest_krbtgt.TargetName.Buffer,
iTargetNameLen_krbtgt);
pKerbRetrieveTktRequest_krbtgt->TargetName.Buffer =
(PWSTR)pTargetNameBuf_krbtgt;

PKERB_RETRIEVE_TKT_RESPONSE pKerbRetrieveTktResponse_krbtgt =
NULL;
ULONG ulReturnBufferLength_krbtgt = 0;
NTSTATUS protocolStatus_krbtgt = 0;
status = LsaCallAuthenticationPackage(
hLsa, // [in] HANDLE
LsaHandle,
ulAuthenticationPackage, // [in] ULONG
AuthenticationPackage,
(PVOID)pKerbRetrieveTktRequest_krbtgt, // [in] PVOID
ProtocolSubmitBuffer,
iTotalLen_krbtgt, // [in] ULONG
SubmitBufferLength,
(PVOID*)&pKerbRetrieveTktResponse_krbtgt, // [out] PVOID*
ProtocolReturnBuffer,
&ulReturnBufferLength_krbtgt, // [out] PULONG
ReturnBufferLength,
&protocolStatus_krbtgt // [out] PNTSTATUS
ProtocolStatus
);

//NOTE: Is it maybe trivial to renew a ticket, if I just pass 0
for TicketFlags,
// CacheOptions and EncryptionType ? (According to MSDN only
if these are 0,
// a ticket that was not already in the cache will be put
there)
// Apparently not: experimentally that had no impact on
ticket lifetimes.

if (status || protocolStatus_krbtgt) {
WRITELN
"LsaCallAuthenticationPackage(KERB_RETRIEVE_TKT_REQ UEST, krbtgt)"
"returned with status 0x%08x and protocol status 0x%08x",
status, protocolStatus_krbtgt);
if (status) return status;
return protocolStatus_krbtgt;
}

//------------------------------------------------------------------------------
// Get token source (needed for renewing further below).
//------------------------------------------------------------------------------

TOKEN_SOURCE tokenSource;
DWORD dwReqSize = sizeof (TOKEN_SOURCE);
BOOL bOk = GetTokenInformation (
ghUserToken, // HANDLE TokenHandle
TokenSource, // TOKEN_INFORMATION_CLASS
TokenInformationClass
&tokenSource, // LPVOID TokenInformation
dwReqSize, // DWORD TokenInformationLength
&dwReqSize // PDWORD ReturnLength
);
if (!bOk) {
WRITELN "GetTokenInformation() returned false");
return 1;
}

//------------------------------------------------------------------------------
// Renew tickets by calling LsaLogonUser with
KERB_TICKET_UNLOCK_LOGON.
//------------------------------------------------------------------------------

KERB_TICKET_UNLOCK_LOGON kerbTicketUnlockLogon = {
//
KERB_TICKET_LOGON Logon:
{

KerbTicketUnlockLogon, //
KERB_LOGON_SUBMIT_TYPE MessageType

0, // ULONG
Flags
pKerbRetrieveTktResponse_host-
>Ticket.EncodedTicketSize, // ULONG ServiceTicketLength

pKerbRetrieveTktResponse_krbtgt-
>Ticket.EncodedTicketSize, // ULONG TicketGrantingTicketLength

pKerbRetrieveTktResponse_host-
>Ticket.EncodedTicket, // PUCHAR ServiceTicket

pKerbRetrieveTktResponse_krbtgt-
>Ticket.EncodedTicket // PUCHAR TicketGrantingTicket

},

gLUID //
LUID LogonId
};

//NOTE: Tried KERB_LOGON_FLAG_ALLOW_EXPIRED_TICKET in Flags, but
then
// LsaLogonUser always returns status
STATUS_INVALID_PARAMETER.

//NOTE: Also tried to set TicketGrantingTicketLength to 0 (and
ticket to NULL),
// but then LsaLogonUser always returns status
STATUS_INVALID_PARAMETER.

// Put struct and encoded ticket buffer immediately after each
other.

int iAuthInfoLen = sizeof(KERB_TICKET_UNLOCK_LOGON);
int iServiceTicketLen =
kerbTicketUnlockLogon.Logon.ServiceTicketLength;
int iTicketGrantingTicketLen =
kerbTicketUnlockLogon.Logon.TicketGrantingTicketLe ngth;
int iTotalLen = iAuthInfoLen + iServiceTicketLen +
iTicketGrantingTicketLen;
KERB_TICKET_UNLOCK_LOGON* pKerbTicketUnlockLogon =
(KERB_TICKET_UNLOCK_LOGON*) malloc (iTotalLen);
memcpy (pKerbTicketUnlockLogon, &kerbTicketUnlockLogon,
iAuthInfoLen);
void* pServiceTicketBuf = (unsigned char*)pKerbTicketUnlockLogon +
iAuthInfoLen;
memcpy (pServiceTicketBuf,
kerbTicketUnlockLogon.Logon.ServiceTicket, iServiceTicketLen);
pKerbTicketUnlockLogon->Logon.ServiceTicket =
(PUCHAR)pServiceTicketBuf;
void* pTicketGrantingTicketBuf = (unsigned
char*)pKerbTicketUnlockLogon + iAuthInfoLen + iServiceTicketLen;
memcpy (pTicketGrantingTicketBuf,
kerbTicketUnlockLogon.Logon.TicketGrantingTicket,
iTicketGrantingTicketLen);
pKerbTicketUnlockLogon->Logon.TicketGrantingTicket =
(PUCHAR)pTicketGrantingTicketBuf;

LSA_STRING originName;
setLsaString (&originName, "anything"); //TODO: More meaningful
string.
LUID newLUID;
HANDLE newUserToken;
PVOID pKerbTicketProfile = NULL; // KERB_TICKET_PROFILE
ULONG ulKerbTicketProfileLen = 0;
QUOTA_LIMITS quotas;
NTSTATUS subStatus;
status = LsaLogonUser(
hLsa, // [in] HANDLE LsaHandle
&originName, // [in] PLSA_STRING
OriginName
Unlock, // [in] SECURITY_LOGON_TYPE
LogonType,
ulAuthenticationPackage, // [in] ULONG
AuthenticationPackage
(PVOID)pKerbTicketUnlockLogon, // [in] PVOID
AuthenticationInformation
(ULONG)iTotalLen, // [in] ULONG
AuthenticationInformationLength
NULL, // [in] PTOKEN_GROUPS
LocalGroups
&tokenSource, // [in] PTOKEN_SOURCE
SourceContext
&pKerbTicketProfile, // [out] PVOID* ProfileBuffer
&ulKerbTicketProfileLen, // [out] PULONG
ProfileBufferLength
&newLUID, // [out] PLUID LogonId
&newUserToken, // [out] PHANDLE Token
&quotas, // [out[ PQUOTA_LIMITS Quotas
&subStatus // [out] PNTSTATUS SubStatus
);

//NOTE: Apparently necessary to pass total length with ticket data
immediately
// after the struct, else get status STATUS_INVALID_PARAMETER
(0xc000000d).

//PROBLEM: So far returns STATUS_INSUFFICIENT_RESOURCES
(0xc000009a),
// "Insufficient system resources exist to complete the
API.",
// A quite generic message that apparently also occurs for
example
// when the kernel is out of memory.

// Is this an intrinsic/hard problem or just a matter of chosing
parameters ?

if (status || subStatus) {
WRITELN "LsaLogonUser(KERB_TICKET_UNLOCK_LOGON)"
"returned with status 0x%08x and sub status 0x%08x",
status, subStatus);
if (status) return status;
return subStatus;
}

//TODO: Assign newLUID and newUserToken to globals ?

//PS: Tried KERB_S4U_LOGON, but get error
STATUS_INVALID_LOGON_TYPE (0xC000010BL),
// which is expected, because this logon type is according to
MSDN only for
// severs (Windows Server 2003, Windows Server 2008).

//TODO: Convert NTSTATUS in error case to Windows error, using
LsaNtStatusToWinError().
//TODO: LsaClose(), free memory...

WRITELN "Kerberos tickets have been renewed.");

return status;
}