Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4236 lines
112 KiB

//+-----------------------------------------------------------------------
//
// File: pac.cxx
//
// Contents: KDC Pac generation code.
//
//
// History: 16-Jan-93 WadeR Created.
//
//------------------------------------------------------------------------
#include "kdcsvr.hxx"
#include <pac.hxx>
#include "kdctrace.h"
#include "fileno.h"
#include <userall.h>
#include <sddl.h>
#include <utils.hxx>
#define FILENO FILENO_GETAS
SECURITY_DESCRIPTOR AuthenticationSD;
#ifndef DONT_SUPPORT_OLD_TYPES
#define KDC_PAC_KEYTYPE KERB_ETYPE_RC4_HMAC_OLD
#define KDC_PAC_CHECKSUM KERB_CHECKSUM_HMAC_MD5
#else
#define KDC_PAC_KEYTYPE KERB_ETYPE_RC4_HMAC
#define KDC_PAC_CHECKSUM KERB_CHECKSUM_HMAC_MD5
#endif
//+-------------------------------------------------------------------------
//
// Function: EnterApiCall
//
// Synopsis: Makes sure that the KDC service is initialized and running
// and won't terminate during the call.
//
// Effects: increments the CurrentApiCallers count.
//
// Arguments:
//
// Requires:
//
// Returns: STATUS_INVALID_SERVER_STATE - the KDC service is not
// running
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
EnterApiCall(
VOID
)
{
NTSTATUS hrRet = STATUS_SUCCESS;
EnterCriticalSection(&ApiCriticalSection);
if (KdcState != Stopped)
{
CurrentApiCallers++;
}
else
{
hrRet = STATUS_INVALID_SERVER_STATE;
}
LeaveCriticalSection(&ApiCriticalSection);
return(hrRet);
}
//+-------------------------------------------------------------------------
//
// Function: LeaveApiCall
//
// Synopsis: Decrements the count of active calls and if the KDC is
// shutting down sets an event to let it continue.
//
// Effects: Deccrements the CurrentApiCallers count.
//
// Arguments:
//
// Requires:
//
// Returns: Nothing
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
LeaveApiCall(
VOID
)
{
EnterCriticalSection(&ApiCriticalSection);
CurrentApiCallers--;
if (KdcState == Stopped)
{
if (CurrentApiCallers == 0)
{
if (!SetEvent(hKdcShutdownEvent))
{
D_DebugLog((DEB_ERROR,"Failed to set shutdown event from LeaveApiCall: 0x%d\n",GetLastError()));
}
else
{
UpdateStatus(SERVICE_STOP_PENDING);
}
//
// Free any DS libraries in use
//
SecData.Cleanup();
if (KdcTraceRegistrationHandle != (TRACEHANDLE)0)
{
UnregisterTraceGuids( KdcTraceRegistrationHandle );
KdcTraceRegistrationHandle = (TRACEHANDLE)0;
}
}
}
LeaveCriticalSection(&ApiCriticalSection);
}
//+-------------------------------------------------------------------------
//
// Function: KdcInsertPacIntoAuthData
//
// Synopsis: Inserts the PAC into the auth data in the two places
// it lives - in the IF_RELEVANT portion & in the outer body
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcInsertPacIntoAuthData(
IN PKERB_AUTHORIZATION_DATA AuthData,
IN PKERB_IF_RELEVANT_AUTH_DATA IfRelevantData,
IN PKERB_AUTHORIZATION_DATA PacAuthData,
OUT PKERB_AUTHORIZATION_DATA * UpdatedAuthData
)
{
KERBERR KerbErr = KDC_ERR_NONE;
PKERB_AUTHORIZATION_DATA LocalAuthData = NULL;
PKERB_AUTHORIZATION_DATA LocalIfRelevantData = NULL;
PKERB_AUTHORIZATION_DATA NewIfRelevantData = NULL;
PKERB_AUTHORIZATION_DATA NewPacData = NULL;
KERB_AUTHORIZATION_DATA TempPacData = {0};
PKERB_AUTHORIZATION_DATA NewAuthData = NULL;
KERB_AUTHORIZATION_DATA TempOldPac = {0};
PKERB_AUTHORIZATION_DATA TempNextPointer,NextPointer;
NewPacData = (PKERB_AUTHORIZATION_DATA) MIDL_user_allocate(sizeof(KERB_AUTHORIZATION_DATA));
NewIfRelevantData = (PKERB_AUTHORIZATION_DATA) MIDL_user_allocate(sizeof(KERB_AUTHORIZATION_DATA));
if ((NewPacData == NULL) || (NewIfRelevantData == NULL))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlZeroMemory(
NewPacData,
sizeof(KERB_AUTHORIZATION_DATA)
);
RtlZeroMemory(
NewIfRelevantData,
sizeof(KERB_AUTHORIZATION_DATA)
);
//
// First build the IfRelevantData
//
// The general idea is to replace, in line, the relevant authorization
// data. This means (a) putting it into the IfRelevantData or making
// the IfRelevantData be PacAuthData, and (b) putting it into AuthData
// as well as changing the IfRelevant portions of that data
//
if (IfRelevantData != NULL)
{
LocalAuthData = KerbFindAuthDataEntry(
KERB_AUTH_DATA_PAC,
IfRelevantData
);
if (LocalAuthData == NULL)
{
LocalIfRelevantData = PacAuthData;
PacAuthData->next = IfRelevantData;
}
else
{
//
// Replace the pac in the if-relevant list with the
// new one.
//
TempOldPac = *LocalAuthData;
LocalAuthData->value.auth_data.value = PacAuthData->value.auth_data.value;
LocalAuthData->value.auth_data.length = PacAuthData->value.auth_data.length;
LocalIfRelevantData = IfRelevantData;
}
}
else
{
//
// build a new if-relevant data
//
TempPacData = *PacAuthData;
TempPacData.next = NULL;
LocalIfRelevantData = &TempPacData;
}
//
// Build a local if-relevant auth data
//
KerbErr = KerbPackData(
&LocalIfRelevantData,
PKERB_IF_RELEVANT_AUTH_DATA_PDU,
(PULONG) &NewIfRelevantData->value.auth_data.length,
&NewIfRelevantData->value.auth_data.value
);
//
// fixup the old if-relevant list, if necessary
//
if (TempOldPac.value.auth_data.value != NULL)
{
*LocalAuthData = TempOldPac;
}
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
NewIfRelevantData->value.auth_data_type = KERB_AUTH_DATA_IF_RELEVANT;
*NewPacData = *PacAuthData;
//
// Zero this out so the old data doesn't get used
//
PacAuthData->value.auth_data.value = NULL;
PacAuthData->value.auth_data.length = 0;
//
// Now we have a new if_relevant & a new pac for the outer auth-data list.
//
NewAuthData = NewIfRelevantData;
NewIfRelevantData->next = NULL;
NewIfRelevantData = NULL;
//
// Start building the list, first putting the non-pac entries at the end
//
NextPointer = AuthData;
while (NextPointer != NULL)
{
if ((NextPointer->value.auth_data_type != KERB_AUTH_DATA_IF_RELEVANT) &&
(NextPointer->value.auth_data_type != KERB_AUTH_DATA_PAC))
{
TempNextPointer = NextPointer->next;
NextPointer->next = NULL;
KerbErr = KerbCopyAndAppendAuthData(
&NewAuthData,
NextPointer
);
NextPointer->next = TempNextPointer;
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
}
NextPointer = NextPointer->next;
}
*UpdatedAuthData = NewAuthData;
NewAuthData = NULL;
Cleanup:
if (NewPacData != NULL)
{
KerbFreeAuthData(NewPacData);
}
if (NewIfRelevantData != NULL)
{
KerbFreeAuthData(NewIfRelevantData);
}
if (NewAuthData != NULL)
{
KerbFreeAuthData(NewAuthData);
}
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcBuildPacSidList
//
// Synopsis: Builds a list of SIDs in the PAC
//
// Effects:
//
// Arguments: UserInfo - validation information
// AddEveryone - add "Everyone" and "Authenticated User" SIDs?
// CrossOrganization - add "Other Org" SID?
// Sids - used to return the resulting SIDs
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcBuildPacSidList(
IN PNETLOGON_VALIDATION_SAM_INFO3 UserInfo,
IN BOOLEAN AddEveryone,
IN BOOLEAN CrossOrganization,
OUT PSAMPR_PSID_ARRAY Sids
)
{
KERBERR KerbErr = KDC_ERR_NONE;
ULONG Size = 0, i;
Sids->Count = 0;
Sids->Sids = NULL;
if (UserInfo->UserId != 0)
{
Size += sizeof(SAMPR_SID_INFORMATION);
}
if (AddEveryone)
{
Size += (sizeof(SAMPR_SID_INFORMATION) * 2);
}
if (CrossOrganization)
{
Size += sizeof(SAMPR_SID_INFORMATION);
}
Size += UserInfo->GroupCount * (ULONG)sizeof(SAMPR_SID_INFORMATION);
//
// If there are extra SIDs, add space for them
//
if (UserInfo->UserFlags & LOGON_EXTRA_SIDS) {
Size += UserInfo->SidCount * (ULONG)sizeof(SAMPR_SID_INFORMATION);
}
Sids->Sids = (PSAMPR_SID_INFORMATION) MIDL_user_allocate( Size );
if ( Sids->Sids == NULL ) {
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlZeroMemory(
Sids->Sids,
Size
);
//
// Start copying SIDs into the structure
//
i = 0;
//
// If the UserId is non-zero, then it contians the users RID.
//
if ( UserInfo->UserId ) {
Sids->Sids[0].SidPointer = (PRPC_SID)
KerbMakeDomainRelativeSid( UserInfo->LogonDomainId,
UserInfo->UserId );
if( Sids->Sids[0].SidPointer == NULL ) {
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Sids->Count++;
}
//
// Copy over all the groups passed as RIDs
//
for ( i=0; i < UserInfo->GroupCount; i++ ) {
Sids->Sids[Sids->Count].SidPointer = (PRPC_SID)
KerbMakeDomainRelativeSid(
UserInfo->LogonDomainId,
UserInfo->GroupIds[i].RelativeId );
if( Sids->Sids[Sids->Count].SidPointer == NULL ) {
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Sids->Count++;
}
//
// Add in the extra SIDs
//
//
// No need to allocate these, but...
//
if (UserInfo->UserFlags & LOGON_EXTRA_SIDS) {
for ( i = 0; i < UserInfo->SidCount; i++ ) {
if (!NT_SUCCESS(KerbDuplicateSid(
(PSID *) &Sids->Sids[Sids->Count].SidPointer,
UserInfo->ExtraSids[i].Sid
)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Sids->Count++;
}
}
//
// Add in everyone, and authenticated users.
//
if ( AddEveryone )
{
if (!NT_SUCCESS(KerbDuplicateSid(
(PSID*) &Sids->Sids[Sids->Count].SidPointer,
GlobalEveryoneSid
)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Sids->Count++;
if (!NT_SUCCESS(KerbDuplicateSid(
(PSID*) &Sids->Sids[Sids->Count].SidPointer,
GlobalAuthenticatedUserSid
)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Sids->Count++;
}
//
// Add in the "Other Organization" SID
//
if ( CrossOrganization )
{
if (!NT_SUCCESS(KerbDuplicateSid(
(PSID*) &Sids->Sids[Sids->Count].SidPointer,
GlobalOtherOrganizationSid )))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Sids->Count++;
}
Cleanup:
if (!KERB_SUCCESS(KerbErr))
{
if (Sids->Sids != NULL)
{
for (i = 0; i < Sids->Count ;i++ )
{
if (Sids->Sids[i].SidPointer != NULL)
{
MIDL_user_free(Sids->Sids[i].SidPointer);
}
}
MIDL_user_free(Sids->Sids);
Sids->Sids = NULL;
Sids->Count = 0;
}
}
return KerbErr;
}
//+-------------------------------------------------------------------------
//
// Function: KdcAddResourceGroupsToPac
//
// Synopsis: Queries SAM for resources groups and builds a new PAC with
// those groups
//
// Effects: Adds Domain Local and Universal groups **IN NATIVE MODE ONLY**
// Only called when you reach domain of target service.
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcAddResourceGroupsToPac(
IN PPACTYPE OldPac,
IN BOOLEAN DcTarget,
IN ULONG ChecksumSize,
OUT PPACTYPE * NewPac
)
{
NTSTATUS Status;
KERBERR KerbErr = KDC_ERR_NONE;
PPAC_INFO_BUFFER LogonInfo;
ULONG Index;
PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo = NULL;
SAMPR_PSID_ARRAY SidList = {0};
PSAMPR_PSID_ARRAY ResourceGroups = NULL;
//
// First, find the logon information
//
LogonInfo = PAC_Find(
OldPac,
PAC_LOGON_INFO,
NULL
);
if (LogonInfo == NULL)
{
D_DebugLog((DEB_WARN,"No logon info for PAC - not adding resource groups\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Now unmarshall the validation information and build a list of sids
//
if (!NT_SUCCESS(PAC_UnmarshallValidationInfo(
&ValidationInfo,
LogonInfo->Data,
LogonInfo->cbBufferSize)))
{
D_DebugLog((DEB_ERROR,"Failed to unmarshall validation info!\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
KerbErr = KdcBuildPacSidList(
ValidationInfo,
FALSE,
FALSE,
&SidList
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Call SAM to get the sids
//
Status = SamIGetResourceGroupMembershipsTransitive(
GlobalAccountDomainHandle,
&SidList,
(DcTarget ? SAM_SERVICE_TARGET_IS_DC : 0),
&ResourceGroups
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to get resource groups: 0x%x\n",Status));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Now build a new pac
//
Status = PAC_InitAndUpdateGroups(
ValidationInfo,
ResourceGroups,
OldPac,
NewPac
);
if (!NT_SUCCESS(Status))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Cleanup:
if (ValidationInfo != NULL)
{
MIDL_user_free(ValidationInfo);
}
if (SidList.Sids != NULL)
{
for (Index = 0; Index < SidList.Count ;Index++ )
{
if (SidList.Sids[Index].SidPointer != NULL)
{
MIDL_user_free(SidList.Sids[Index].SidPointer);
}
}
MIDL_user_free(SidList.Sids);
}
SamIFreeSidArray(
ResourceGroups
);
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcSignPac
//
// Synopsis: Signs a PAC by first checksumming it with the
// server's key and then signing that with the KDC key.
//
// Effects: Modifies the server sig & privsvr sig fields of the PAC
//
// Arguments: ServerInfo - Ticket info for the server, used
// for the initial signature
// PacData - An marshalled PAC.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcSignPac(
IN PKERB_ENCRYPTION_KEY ServerKey,
IN BOOLEAN AddResourceGroups,
IN BOOLEAN DCTarget,
IN OUT PUCHAR * PacData,
IN PULONG PacSize
)
{
NTSTATUS Status;
KERBERR KerbErr = KDC_ERR_NONE;
PCHECKSUM_FUNCTION Check = NULL ;
PCHECKSUM_BUFFER CheckBuffer = NULL;
PPAC_INFO_BUFFER ServerBuffer;
PPAC_INFO_BUFFER PrivSvrBuffer;
PPAC_SIGNATURE_DATA ServerSignature;
PPAC_SIGNATURE_DATA PrivSvrSignature;
PKERB_ENCRYPTION_KEY EncryptionKey;
PPACTYPE Pac = NULL, NewPac = NULL;
ULONG LocalPacSize = 0;
KDC_TICKET_INFO KdcTicketInfo = {0};
BOOL PacUnmarshalled = FALSE;
TRACE(KDC, KdcSignPac, DEB_FUNCTION);
KerbErr = SecData.GetKrbtgtTicketInfo(&KdcTicketInfo);
if (!KERB_SUCCESS(KerbErr))
{
Status = KerbMapKerbError(KerbErr);
goto Cleanup;
}
//
// Locate the checksum used to sign the PAC.
//
Status = CDLocateCheckSum(
(ULONG) KDC_PAC_CHECKSUM,
&Check
);
if (!NT_SUCCESS(Status))
{
KerbErr = KDC_ERR_SUMTYPE_NOSUPP;
goto Cleanup;
}
//
// Unmarshal the PAC in place so we can locate the signatuer buffers
//
Pac = (PPACTYPE) *PacData;
LocalPacSize = *PacSize;
if (PAC_UnMarshal(Pac, LocalPacSize) == 0)
{
D_DebugLog((DEB_ERROR,"Failed to unmarshal pac\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
PacUnmarshalled = TRUE;
//
// If we are to add local groups, do so now
//
if (AddResourceGroups)
{
KerbErr = KdcAddResourceGroupsToPac(
Pac,
DCTarget,
Check->CheckSumSize,
&NewPac
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
Pac = NewPac;
LocalPacSize = PAC_GetSize(Pac);
}
//
// Locate the signature buffers so the signature fields can be zeroed out
// before computing the checksum.
//
ServerBuffer = PAC_Find(Pac, PAC_SERVER_CHECKSUM, NULL );
DsysAssert(ServerBuffer != NULL);
if (ServerBuffer == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
ServerSignature = (PPAC_SIGNATURE_DATA) ServerBuffer->Data;
ServerSignature->SignatureType = (ULONG) KDC_PAC_CHECKSUM;
RtlZeroMemory(
ServerSignature->Signature,
PAC_CHECKSUM_SIZE(ServerBuffer->cbBufferSize)
);
PrivSvrBuffer = PAC_Find(Pac, PAC_PRIVSVR_CHECKSUM, NULL );
DsysAssert(PrivSvrBuffer != NULL);
if (PrivSvrBuffer == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
PrivSvrSignature = (PPAC_SIGNATURE_DATA) PrivSvrBuffer->Data;
PrivSvrSignature->SignatureType = (ULONG) KDC_PAC_CHECKSUM;
RtlZeroMemory(
PrivSvrSignature->Signature,
PAC_CHECKSUM_SIZE(PrivSvrBuffer->cbBufferSize)
);
//
// Now remarshall the PAC to compute the checksum.
//
if (!PAC_ReMarshal(Pac, LocalPacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
PacUnmarshalled = FALSE;
//
// Now compute the signatures on the PAC. First we compute the checksum
// of the whole PAC.
//
if (NULL != Check->InitializeEx2)
{
Status = Check->InitializeEx2(
ServerKey->keyvalue.value,
ServerKey->keyvalue.length,
NULL,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
else
{
Status = Check->InitializeEx(
ServerKey->keyvalue.value,
ServerKey->keyvalue.length,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
if (!NT_SUCCESS(Status))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Check->Sum(
CheckBuffer,
LocalPacSize,
(PUCHAR) Pac
);
Check->Finalize(
CheckBuffer,
ServerSignature->Signature
);
Check->Finish(
&CheckBuffer
);
//
// Now we've compute the server checksum - next compute the checksum
// of the server checksum using the KDC account.
//
//
// Get the key used to sign pacs.
//
EncryptionKey = KerbGetKeyFromList(
KdcTicketInfo.Passwords,
(ULONG) KDC_PAC_KEYTYPE
);
if (EncryptionKey == NULL)
{
Status = SEC_E_ETYPE_NOT_SUPP;
goto Cleanup;
}
if (NULL != Check->InitializeEx2)
{
Status = Check->InitializeEx2(
EncryptionKey->keyvalue.value,
EncryptionKey->keyvalue.length,
NULL,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
else
{
Status = Check->InitializeEx(
EncryptionKey->keyvalue.value,
EncryptionKey->keyvalue.length,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
if (!NT_SUCCESS(Status))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Check->Sum(
CheckBuffer,
Check->CheckSumSize,
ServerSignature->Signature
);
Check->Finalize(
CheckBuffer,
PrivSvrSignature->Signature
);
Check->Finish(
&CheckBuffer
);
if (*PacData != (PBYTE) Pac)
{
MIDL_user_free(*PacData);
*PacData = (PBYTE) Pac;
*PacSize = LocalPacSize;
}
Cleanup:
if ( PacUnmarshalled )
{
if (!PAC_ReMarshal(Pac, LocalPacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
}
}
if ( ( CheckBuffer != NULL ) &&
( Check != NULL ) )
{
Check->Finish(&CheckBuffer);
}
if (!KERB_SUCCESS(KerbErr) && (NewPac != NULL))
{
MIDL_user_free(NewPac);
}
FreeTicketInfo(&KdcTicketInfo);
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcVerifyPacSignature
//
// Synopsis: Verifies a PAC by checksumming it and comparing the result
// with the server checksum. In addition, if the pac wasn't
// created by another realm (server ticket info is not
// an interdomain account) verify the KDC signature on the
// pac.
//
// Effects:
//
// Arguments: ServerInfo - Ticket info for the server, used
// for the initial signature
// Pac - An unmarshalled PAC.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcVerifyPacSignature(
IN PKERB_ENCRYPTION_KEY ServerKey,
IN PKDC_TICKET_INFO ServerInfo,
IN ULONG PacSize,
IN PUCHAR PacData
)
{
NTSTATUS Status;
KERBERR KerbErr = KDC_ERR_NONE;
PCHECKSUM_FUNCTION Check = NULL ;
PCHECKSUM_BUFFER CheckBuffer = NULL;
PKERB_ENCRYPTION_KEY EncryptionKey = NULL;
PPAC_INFO_BUFFER ServerBuffer;
PPAC_INFO_BUFFER PrivSvrBuffer;
PPAC_SIGNATURE_DATA ServerSignature;
PPAC_SIGNATURE_DATA PrivSvrSignature;
UCHAR LocalChecksum[20];
UCHAR LocalServerChecksum[20];
UCHAR LocalPrivSvrChecksum[20];
PPACTYPE Pac;
KDC_TICKET_INFO KdcTicketInfo = {0};
BOOL PacUnmarshalled = FALSE;
TRACE(KDC, KdcVerifyPacSignature, DEB_FUNCTION);
Pac = (PPACTYPE) PacData;
if (PAC_UnMarshal(Pac, PacSize) == 0)
{
D_DebugLog((DEB_ERROR,"Failed to unmarshal pac\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
PacUnmarshalled = TRUE;
KerbErr = SecData.GetKrbtgtTicketInfo(&KdcTicketInfo);
if (!KERB_SUCCESS(KerbErr))
{
Status = KerbMapKerbError(KerbErr);
goto Cleanup;
}
//
// Locate the two signatures, copy the checksum, and zero the value
// so the checksum won't include the old checksums.
//
ServerBuffer = PAC_Find(Pac, PAC_SERVER_CHECKSUM, NULL );
DsysAssert(ServerBuffer != NULL);
if ((ServerBuffer == NULL) || (ServerBuffer->cbBufferSize < PAC_SIGNATURE_SIZE(0)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
ServerSignature = (PPAC_SIGNATURE_DATA) ServerBuffer->Data;
RtlCopyMemory(
LocalServerChecksum,
ServerSignature->Signature,
PAC_CHECKSUM_SIZE(ServerBuffer->cbBufferSize)
);
RtlZeroMemory(
ServerSignature->Signature,
PAC_CHECKSUM_SIZE(ServerBuffer->cbBufferSize)
);
PrivSvrBuffer = PAC_Find(Pac, PAC_PRIVSVR_CHECKSUM, NULL );
DsysAssert(PrivSvrBuffer != NULL);
if ((PrivSvrBuffer == NULL) || (PrivSvrBuffer->cbBufferSize < PAC_SIGNATURE_SIZE(0)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
PrivSvrSignature = (PPAC_SIGNATURE_DATA) PrivSvrBuffer->Data;
RtlCopyMemory(
LocalPrivSvrChecksum,
PrivSvrSignature->Signature,
PAC_CHECKSUM_SIZE(PrivSvrBuffer->cbBufferSize)
);
RtlZeroMemory(
PrivSvrSignature->Signature,
PAC_CHECKSUM_SIZE(PrivSvrBuffer->cbBufferSize)
);
//
// Remarshal the pac so we can checksum it.
//
if (!PAC_ReMarshal(Pac, PacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
PacUnmarshalled = FALSE;
//
// Now compute the signatures on the PAC. First we compute the checksum
// of the validation information using the server's key.
//
//
// Locate the checksum used to sign the PAC.
//
Status = CDLocateCheckSum(
ServerSignature->SignatureType,
&Check
);
if (!NT_SUCCESS(Status))
{
KerbErr = KDC_ERR_SUMTYPE_NOSUPP;
goto Cleanup;
}
if (Check->CheckSumSize > sizeof(LocalChecksum)) {
DsysAssert(Check->CheckSumSize <= sizeof(LocalChecksum));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// if available use the Ex2 version for keyed checksums where checksum
// must be passed in on verification
//
if (NULL != Check->InitializeEx2)
{
Status = Check->InitializeEx2(
ServerKey->keyvalue.value,
ServerKey->keyvalue.length,
LocalServerChecksum,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
else
{
Status = Check->InitializeEx(
ServerKey->keyvalue.value,
ServerKey->keyvalue.length,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
if (!NT_SUCCESS(Status))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Check->Sum(
CheckBuffer,
PacSize,
PacData
);
Check->Finalize(
CheckBuffer,
LocalChecksum
);
Check->Finish(
&CheckBuffer
);
if (Check->CheckSumSize != PAC_CHECKSUM_SIZE(ServerBuffer->cbBufferSize) ||
!RtlEqualMemory(
LocalChecksum,
LocalServerChecksum,
Check->CheckSumSize))
{
DebugLog((DEB_ERROR, "Pac was modified - server checksum doesn't match\n"));
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
//
// If the service wasn't the KDC and it wasn't an interdomain account
// verify the KDC checksum.
//
if ((ServerInfo->UserId == DOMAIN_USER_RID_KRBTGT) ||
((ServerInfo->UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT) != 0))
{
goto Cleanup;
}
//
// Get the key used to sign pacs.
//
EncryptionKey = KerbGetKeyFromList(
KdcTicketInfo.Passwords,
(ULONG) KDC_PAC_KEYTYPE
);
if (EncryptionKey == NULL)
{
Status = SEC_E_ETYPE_NOT_SUPP;
goto Cleanup;
}
//
// Locate the checksum used to sign the PAC.
//
Status = CDLocateCheckSum(
PrivSvrSignature->SignatureType,
&Check
);
if (!NT_SUCCESS(Status))
{
KerbErr = KDC_ERR_SUMTYPE_NOSUPP;
goto Cleanup;
}
//
// if available use the Ex2 version for keyed checksums where checksum
// must be passed in on verification
//
if (NULL != Check->InitializeEx2)
{
Status = Check->InitializeEx2(
EncryptionKey->keyvalue.value,
EncryptionKey->keyvalue.length,
LocalPrivSvrChecksum,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
else
{
Status = Check->InitializeEx(
EncryptionKey->keyvalue.value,
EncryptionKey->keyvalue.length,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
if (!NT_SUCCESS(Status))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Check->Sum(
CheckBuffer,
Check->CheckSumSize,
LocalServerChecksum
);
Check->Finalize(
CheckBuffer,
LocalChecksum
);
Check->Finish(
&CheckBuffer
);
if ((Check->CheckSumSize != PAC_CHECKSUM_SIZE(PrivSvrBuffer->cbBufferSize)) ||
!RtlEqualMemory(
LocalChecksum,
LocalPrivSvrChecksum,
Check->CheckSumSize))
{
DebugLog((DEB_ERROR, "Pac was modified - privsvr checksum doesn't match\n"));
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
Cleanup:
if ( PacUnmarshalled )
{
if (!PAC_ReMarshal(Pac, PacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
}
}
if (KerbErr == KRB_AP_ERR_MODIFIED)
{
LPWSTR AccountName = NULL;
AccountName = (LPWSTR) MIDL_user_allocate(ServerInfo->AccountName.Length + sizeof(WCHAR));
//
// if the allocation fails don't log the name (leave it NULL)
//
if (NULL != AccountName)
{
RtlCopyMemory(
AccountName,
ServerInfo->AccountName.Buffer,
ServerInfo->AccountName.Length
);
}
ReportServiceEvent(
EVENTLOG_ERROR_TYPE,
KDCEVENT_PAC_VERIFICATION_FAILURE,
sizeof(ULONG),
&KerbErr,
1,
AccountName
);
if (NULL != AccountName)
{
MIDL_user_free(AccountName);
}
}
if ( ( CheckBuffer != NULL ) &&
( Check != NULL ) )
{
Check->Finish(&CheckBuffer);
}
FreeTicketInfo(&KdcTicketInfo);
return(KerbErr);
}
//+---------------------------------------------------------------------------
//
// Name: KdcGetPacAuthData
//
// Synopsis: Creates a PAC for the specified client, encrypts it with the
// server's key, and packs it into a KERB_AUTHORIZATON_DATA
//
// Arguments: UserInfo - Information about user
// GroupMembership - Users group memberships
// ServerKey - Key of server, used for signing
// CredentialKey - if present & valid, used to encrypt supp. creds
// AddResourceGroups - if TRUE, resources groups will be included
// EncryptedTicket - Optional ticke to tie PAC to
// S4UTicketInfo - used only when inserting initial S4U2self auth
// data into the ticket. causes the S4U2self
// target to be stored inside the PAC
// PacAuthData - Receives a KERB_AUTHORIZATION_DATA of type
// KERB_AUTH_DATA_PAC, containing a PAC.
//
// Notes: PacAuthData should be freed with KerbFreeAuthorizationData.
//
//+---------------------------------------------------------------------------
KERBERR
KdcGetPacAuthData(
IN PUSER_INTERNAL6_INFORMATION UserInfo,
IN PSID_AND_ATTRIBUTES_LIST GroupMembership,
IN PKERB_ENCRYPTION_KEY ServerKey,
IN PKERB_ENCRYPTION_KEY CredentialKey,
IN BOOLEAN AddResourceGroups,
IN PKERB_ENCRYPTED_TICKET EncryptedTicket,
IN OPTIONAL PKDC_S4U_TICKET_INFO S4UClientInfo,
OUT PKERB_AUTHORIZATION_DATA * PacAuthData,
OUT PKERB_EXT_ERROR pExtendedError
)
{
KERBERR KerbErr = KDC_ERR_NONE;
PACTYPE *pNewPac = NULL;
KERB_AUTHORIZATION_DATA AuthorizationData = {0};
ULONG PacSize, NameType;
PCHECKSUM_FUNCTION Check;
NTSTATUS Status;
UNICODE_STRING ClientName = {0};
PKERB_INTERNAL_NAME KdcName = NULL;
TimeStamp ClientId;
TRACE(KDC, KdcGetPacAuthData, DEB_FUNCTION);
Status = CDLocateCheckSum(
(ULONG) KDC_PAC_CHECKSUM,
&Check
);
if (!NT_SUCCESS(Status))
{
KerbErr = KDC_ERR_SUMTYPE_NOSUPP;
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
KerbConvertGeneralizedTimeToLargeInt(
&ClientId,
&EncryptedTicket->authtime,
0 // no usec
);
//
// Put the S4U client in the pac verifier. For S4USelf, we use
// user@domain to keep W2K servers / kdcs from allowing xrealm tgts
// w/ s4u pacs
//
if (ARGUMENT_PRESENT(S4UClientInfo))
{
KerbErr = KerbConvertKdcNameToString(
&ClientName,
S4UClientInfo->PACCName,
(((S4UClientInfo->Flags & TI_REQUESTOR_THIS_REALM) != 0) ? NULL : &S4UClientInfo->PACCRealm )
);
}
else // use the ticket
{
KerbErr = KerbConvertPrincipalNameToString(
&ClientName,
&NameType,
&EncryptedTicket->client_name
);
}
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
KerbErr = GetPacAndSuppCred(
UserInfo,
GroupMembership,
Check->CheckSumSize, // leave space for signature
CredentialKey,
&ClientId,
&ClientName,
&pNewPac,
pExtendedError
);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog(( DEB_WARN,
"GetPAC: Can't get PAC or supp creds: 0x%x \n", KerbErr ));
goto Cleanup;
}
//
// The PAC is going to be double-encrypted. This is done by having the
// PAC in an EncryptedData, and having that EncryptedData in a AuthData
// as part of an AuthDataList (along with the rest of the supp creds).
// Finally, the entire list is encrypted.
//
// KERB_AUTHORIZATION_DATA containing {
// PAC
//
// }
//
//
// First build inner encrypted data
//
PacSize = PAC_GetSize( pNewPac );
AuthorizationData.value.auth_data_type = KERB_AUTH_DATA_PAC;
AuthorizationData.value.auth_data.length = PacSize;
AuthorizationData.value.auth_data.value = (PUCHAR) MIDL_user_allocate(PacSize);
if (AuthorizationData.value.auth_data.value == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
PAC_Marshal( pNewPac, PacSize, AuthorizationData.value.auth_data.value );
//
// Compute the signatures
//
KerbErr = KdcSignPac(
ServerKey,
AddResourceGroups,
FALSE, // this is a TGT...
&AuthorizationData.value.auth_data.value,
(PULONG) &AuthorizationData.value.auth_data.length
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Create the auth data to return
//
KerbErr = KdcInsertPacIntoAuthData(
NULL, // no original auth data
NULL, // no if-relevant auth data
&AuthorizationData,
PacAuthData
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR,"Failed to insert pac into new auth data: 0x%x\n",
KerbErr));
goto Cleanup;
}
Cleanup:
if (AuthorizationData.value.auth_data.value != NULL)
{
MIDL_user_free(AuthorizationData.value.auth_data.value);
}
if (pNewPac != NULL)
{
MIDL_user_free(pNewPac);
}
KerbFreeString(&ClientName);
KerbFreeKdcName(&KdcName);
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcGetUserPac
//
// Synopsis: Function for external users to get the PAC for a user
//
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
extern "C"
NTSTATUS
KdcGetUserPac(
IN PUNICODE_STRING UserName,
OUT PPACTYPE * Pac,
OUT PUCHAR * SupplementalCredentials,
OUT PULONG SupplementalCredSize,
OUT PKERB_EXT_ERROR pExtendedError
)
{
KDC_TICKET_INFO TicketInfo;
PUSER_INTERNAL6_INFORMATION UserInfo = NULL;
SID_AND_ATTRIBUTES_LIST GroupMembership;
NTSTATUS Status;
KERBERR KerbErr;
TRACE(KDC, KdcGetUserPac, DEB_FUNCTION);
*SupplementalCredentials = NULL;
*SupplementalCredSize = 0;
RtlZeroMemory(
&TicketInfo,
sizeof(KDC_TICKET_INFO)
);
RtlZeroMemory(
&GroupMembership,
sizeof(SID_AND_ATTRIBUTES_LIST)
);
Status = EnterApiCall();
if (!NT_SUCCESS(Status))
{
return(Status);
}
//
// Get the account information
//
KerbErr = KdcGetTicketInfo(
UserName,
0, // no flags
FALSE, // do not restrict user accounts (user2user)
NULL, // no principal name
NULL, // no realm
&TicketInfo,
pExtendedError,
NULL, // no user handle
USER_ALL_GET_PAC_AND_SUPP_CRED,
0L, // no extended fields
&UserInfo,
&GroupMembership
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_WARN,"Failed to get ticket info for user %wZ: 0x%x\n",
UserName->Buffer, KerbErr));
Status = KerbMapKerbError(KerbErr);
goto Cleanup;
}
//
// Now get the PAC and supplemental credentials
//
KerbErr = GetPacAndSuppCred(
UserInfo,
&GroupMembership,
0, // no signature space
NULL, // no credential key
NULL, // no client ID
NULL, // no client name
Pac,
pExtendedError
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR,"Failed to get PAC for user %wZ : 0x%x\n",
UserName->Buffer,KerbErr));
Status = KerbMapKerbError(KerbErr);
goto Cleanup;
}
Cleanup:
SamIFree_UserInternal6Information( UserInfo );
SamIFreeSidAndAttributesList(&GroupMembership);
FreeTicketInfo(&TicketInfo);
LeaveApiCall();
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KdcVerifyPac
//
// Synopsis: Function for kerberos to pass through a pac signature
// to be verified.
//
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
extern "C"
NTSTATUS
KdcVerifyPac(
IN ULONG ChecksumSize,
IN PUCHAR Checksum,
IN ULONG SignatureType,
IN ULONG SignatureSize,
IN PUCHAR Signature
)
{
NTSTATUS Status;
KERBERR KerbErr;
PCHECKSUM_FUNCTION Check;
PCHECKSUM_BUFFER CheckBuffer = NULL;
UCHAR LocalChecksum[20];
PKERB_ENCRYPTION_KEY EncryptionKey = NULL;
KDC_TICKET_INFO KdcTicketInfo = {0};
TRACE(KDC, KdcVerifyPac, DEB_FUNCTION);
Status = EnterApiCall();
if (!NT_SUCCESS(Status))
{
return(Status);
}
KerbErr = SecData.GetKrbtgtTicketInfo(&KdcTicketInfo);
if (!KERB_SUCCESS(KerbErr))
{
Status = KerbMapKerbError(KerbErr);
goto Cleanup;
}
//
// Get the key used to sign pacs.
//
EncryptionKey = KerbGetKeyFromList(
KdcTicketInfo.Passwords,
(ULONG) KDC_PAC_KEYTYPE
);
if (EncryptionKey == NULL)
{
Status = SEC_E_ETYPE_NOT_SUPP;
goto Cleanup;
}
Status = CDLocateCheckSum(
SignatureType,
&Check
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (Check->CheckSumSize > sizeof(LocalChecksum)) {
DsysAssert(Check->CheckSumSize <= sizeof(LocalChecksum));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// if available use the Ex2 version for keyed checksums where checksum
// must be passed in on verification
//
if (NULL != Check->InitializeEx2)
{
Status = Check->InitializeEx2(
EncryptionKey->keyvalue.value,
EncryptionKey->keyvalue.length,
Signature,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
else
{
Status = Check->InitializeEx(
EncryptionKey->keyvalue.value,
EncryptionKey->keyvalue.length,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Check->Sum(
CheckBuffer,
ChecksumSize,
Checksum
);
Check->Finalize(
CheckBuffer,
LocalChecksum
);
Check->Finish(&CheckBuffer);
//
// Now compare the local checksum to the supplied checksum.
//
if (Check->CheckSumSize != SignatureSize)
{
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
if (!RtlEqualMemory(
LocalChecksum,
Signature,
Check->CheckSumSize
))
{
DebugLog((DEB_ERROR,"Checksum on the PAC does not match!\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
Cleanup:
if (Status == STATUS_LOGON_FAILURE)
{
PUNICODE_STRING OwnName = NULL;
//
// since this call should only be made by pass through callback
// this signature should be our own
//
OwnName = SecData.KdcFullServiceDnsName();
ReportServiceEvent(
EVENTLOG_ERROR_TYPE,
KDCEVENT_PAC_VERIFICATION_FAILURE,
0,
NULL,
1, // number of strings
OwnName->Buffer
);
}
FreeTicketInfo(&KdcTicketInfo);
LeaveApiCall();
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KdcUpdateAndValidateS4UProxyPAC
//
// Synopsis: Validates your target name from original pac, and updates
// existing info.
//
// Effects:
//
// Arguments:
// CLientId - Auth time of ticket.
// CName - Client name to put into verifier.
// Pac - **UNMARSHALLED** PAC, freed in this function, and
// rebuilt.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KdcReplacePacVerifier(
PTimeStamp ClientId,
PUNICODE_STRING CName,
IN PPACTYPE OldPac,
OUT PPACTYPE *NewPac
)
{
ULONG cbBytes = 0;
ULONG cPacBuffers = 0;
PPAC_INFO_BUFFER Verifier = NULL;
PPACTYPE pNewPac = NULL;
PBYTE pDataStore;
NTSTATUS Status = STATUS_SUCCESS;
ULONG Index, iBuffer = 0;
*NewPac = NULL;
for (Index = 0; Index < OldPac->cBuffers ; Index++ )
{
if (OldPac->Buffers[Index].ulType != PAC_CLIENT_INFO_TYPE)
{
cbBytes += ROUND_UP_COUNT(OldPac->Buffers[Index].cbBufferSize,ALIGN_QUAD);
cPacBuffers++;
}
}
Status = KdcBuildPacVerifier(
ClientId,
CName,
&Verifier
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "BuildPacVerifier failed\n"));
goto Cleanup;
}
cPacBuffers++;
cbBytes += ROUND_UP_COUNT(Verifier->cbBufferSize, ALIGN_QUAD);
//
// We need space for the PAC structure itself. Because the PAC_INFO_BUFFER
// is defined to be an array, a sizeof(PAC) already includes the
// size of ANYSIZE_ARRAY PAC_INFO_BUFFERs so we can subtract some bytes off.
//
cbBytes += sizeof(PACTYPE) +
(cPacBuffers - ANYSIZE_ARRAY) * sizeof(PAC_INFO_BUFFER);
cbBytes = ROUND_UP_COUNT( cbBytes, ALIGN_QUAD );
pNewPac = (PPACTYPE) MIDL_user_allocate( cbBytes );
if (pNewPac == NULL)
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
pNewPac->cBuffers = cPacBuffers;
pDataStore = (PBYTE)&(pNewPac->Buffers[pNewPac->cBuffers]);
pDataStore = (PBYTE) ROUND_UP_POINTER( pDataStore, ALIGN_QUAD );
//
// Copy the data over
//
for (Index = 0; Index < OldPac->cBuffers ; Index++ )
{
if (OldPac->Buffers[Index].ulType != PAC_CLIENT_INFO_TYPE)
{
pNewPac->Buffers[iBuffer].ulType = OldPac->Buffers[Index].ulType;
pNewPac->Buffers[iBuffer].cbBufferSize = OldPac->Buffers[Index].cbBufferSize;
pNewPac->Buffers[iBuffer].Data = pDataStore;
RtlCopyMemory(
pDataStore,
OldPac->Buffers[Index].Data,
OldPac->Buffers[Index].cbBufferSize
);
pDataStore += pNewPac->Buffers[iBuffer].cbBufferSize;
pDataStore = (PBYTE) ROUND_UP_POINTER( pDataStore, ALIGN_QUAD );
iBuffer ++;
}
}
//
// Finally copy over the pac verifier.
//
pNewPac->Buffers[iBuffer].ulType = PAC_CLIENT_INFO_TYPE;
pNewPac->Buffers[iBuffer].cbBufferSize = Verifier->cbBufferSize;
pNewPac->Buffers[iBuffer].Data = pDataStore;
RtlCopyMemory(
pDataStore,
Verifier->Data,
Verifier->cbBufferSize
);
*NewPac = pNewPac;
pNewPac = NULL;
Cleanup:
if ( pNewPac )
{
MIDL_user_free( pNewPac);
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KdcUpdateAndValidateS4UProxyPAC
//
// Synopsis: Validates your target name from original pac, and updates
// existing info.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
#define DEB_TEST_CODE 0xff000000
#define DEB_TEST_CODE2 0x00ff0000
KERBERR
KdcUpdateAndVerifyS4UPacVerifier(
IN PKDC_S4U_TICKET_INFO S4UTicketInfo,
IN OUT PUCHAR *PacData,
IN OUT PULONG PacSize
)
{
PPAC_INFO_BUFFER Verifier = NULL;
PPACTYPE OldPac;
ULONG OldPacSize;
PPACTYPE NewPac = NULL;
NTSTATUS Status;
KERBERR KerbErr = KDC_ERR_NONE;
PPAC_CLIENT_INFO ClientInfo = NULL;
TimeStamp ClientId;
UNICODE_STRING VerifierNames = {0};
UNICODE_STRING VerifierCName = {0};
UNICODE_STRING PreauthCName = {0};
UNICODE_STRING VerifierCRealm = {0};
PWSTR Realm = NULL;
PWSTR CName = NULL;
PPACTYPE RemarshalPac = NULL;
ULONG RemarshalPacSize = 0;
LONG i;
OldPac = (PPACTYPE) *PacData;
OldPacSize = *PacSize;
if (PAC_UnMarshal(OldPac, OldPacSize) == 0)
{
D_DebugLog((DEB_ERROR,"Failed to unmarshal pac\n"));
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
//
// Must remember to remarshal the PAC prior to returning
//
RemarshalPac = OldPac;
RemarshalPacSize = OldPacSize;
Verifier = PAC_Find(
OldPac,
PAC_CLIENT_INFO_TYPE,
NULL
);
if ( Verifier == NULL )
{
DebugLog((DEB_ERROR, "Missing PAC verifier in S4U Tickets\n"));
DsysAssert(FALSE);
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
if ( Verifier->cbBufferSize < sizeof(PAC_CLIENT_INFO) )
{
D_DebugLog((DEB_ERROR, "Clientinfo is too small: %d instead of %d\n", Verifier->cbBufferSize, sizeof(PAC_CLIENT_INFO)));
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
ClientInfo = (PPAC_CLIENT_INFO) Verifier->Data;
if ((ClientInfo->NameLength - ANYSIZE_ARRAY * sizeof(WCHAR) + sizeof(PPAC_CLIENT_INFO)) > Verifier->cbBufferSize)
{
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
KerbConvertGeneralizedTimeToLargeInt(
&ClientId,
&S4UTicketInfo->EvidenceTicket->authtime,
0 // no usec
);
if (!RtlEqualMemory(
&ClientId,
&ClientInfo->ClientId,
sizeof(TimeStamp)
))
{
D_DebugLog((DEB_ERROR, "Client IDs don't match.\n"));
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
//
// Check the name now - for s4uself requests, the name is going
// to be cname@crealm. This was inserted by the KDC of crealm during
// the initial processing of the pa-for-user.
//
// Now, we have to substite in the "w2k" version of the pac verifier,
// as we're granting a service ticket to a s4uself request from our realm.
// So, there are a couple of checks to be done here:
//
//
// 1. Does the PA-FOR-USER cname match that in the verifier?
// 2. Does the PA-FOR-USER crealm match that in the verifier?
// 3. Does the validation info (PAC) cname match that in the verifier?
//
// If so, add in a W2K pac verfier.
//
VerifierNames.Length = ClientInfo->NameLength;
VerifierNames.MaximumLength = ClientInfo->NameLength + sizeof(WCHAR);
SafeAllocaAllocate( VerifierNames.Buffer, VerifierNames.MaximumLength);
if ( VerifierNames.Buffer == NULL )
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlCopyMemory(
VerifierNames.Buffer,
ClientInfo->Name,
VerifierNames.Length
);
VerifierNames.Buffer[(VerifierNames.Length / sizeof(WCHAR))] = L'\0';
//
// Find the @ sign, and split. Search from the end of the string.
//
i = VerifierNames.Length / sizeof(WCHAR);
while (i > 0)
{
if (VerifierNames.Buffer[i] == L'@')
{
VerifierNames.Buffer[i] = L'\0';
if ( i < (LONG) (VerifierNames.Length / sizeof(WCHAR)) )
{
Realm = &VerifierNames.Buffer[i + 1];
CName = VerifierNames.Buffer;
break;
}
}
i--;
}
if ( Realm == NULL )
{
DebugLog((DEB_ERROR, "S4U Pac verifier missing @ sign\n"));
DsysAssert(FALSE);
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
RtlInitUnicodeString(
&VerifierCRealm,
Realm
);
RtlInitUnicodeString(
&VerifierCName,
CName
);
if (!RtlEqualUnicodeString(
&VerifierCRealm,
&S4UTicketInfo->PACCRealm,
TRUE
))
{
DebugLog((DEB_ERROR, "pa-for-user != pac verfier realm\n"));
DsysAssert(FALSE);
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
KerbErr = KerbConvertKdcNameToString(
&PreauthCName,
S4UTicketInfo->PACCName,
NULL
);
if (!KERB_SUCCESS( KerbErr ))
{
goto Cleanup;
}
if (!RtlEqualUnicodeString(
&PreauthCName,
&VerifierCName,
TRUE
))
{
DebugLog((DEB_ERROR, "pa-for-user != pac verifier cname\n"));
DsysAssert(FALSE);
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
//
// Now check the validation information. Fester - netbios and dns names preclude this from
// working...
//
/*LogonInfo = PAC_Find(
OldPac,
PAC_LOGON_INFO,
NULL
);
if ( LogonInfo == NULL )
{
KerbErr = KRB_ERR_GENERIC;
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
Status = PAC_UnmarshallValidationInfo(
&LocalValidationInfo,
LogonInfo->Data,
LogonInfo->cbBufferSize
);
if ( !NT_SUCCESS( Status ))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
if (!RtlEqualUnicodeString(
&LocalValidationInfo->EffectiveName,
&VerifierCName,
TRUE
))
{
DebugLog((DEB_ERROR, "pa-for-user != logon info cname\n"));
DsysAssert(FALSE);
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
//
// Fester - how do we use the Netbios domain name in Validation info
// to validate S4U Pac verifier?
// */
//
// Cool - everything looks good. Now remove the S4U pac verifier, and
// add in the W2K style verfier.
//
Status = KdcReplacePacVerifier(
&ClientId,
&VerifierCName,
OldPac,
&NewPac
);
if (!KERB_SUCCESS( KerbErr ))
{
DebugLog((DEB_ERROR, "KdcAddPacVerifier failed\n"));
DsysAssert(FALSE);
goto Cleanup;
}
MIDL_user_free( OldPac );
RemarshalPacSize = PAC_GetSize(NewPac);
RemarshalPac = NewPac;
NewPac = NULL;
Cleanup:
if ( RemarshalPac != NULL )
{
if (!PAC_ReMarshal(RemarshalPac, RemarshalPacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
}
*PacData = (PBYTE) RemarshalPac;
*PacSize = RemarshalPacSize;
}
SafeAllocaFree( VerifierNames.Buffer );
if (NewPac != NULL)
{
MIDL_user_free(NewPac);
}
KerbFreeString(&PreauthCName);
return KerbErr;
}
//+-------------------------------------------------------------------------
//
// Function: KdcUpdateAndValidateS4UProxyPAC
//
// Synopsis: Validates your target name from original pac, and updates
// existing info.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcUpdateAndValidateS4UProxyPAC(
IN PKDC_S4U_TICKET_INFO S4UTicketInfo,
IN OUT PUCHAR *PacData,
IN OUT PULONG PacSize,
OUT OPTIONAL PS4U_DELEGATION_INFO* S4UDelegationInfo
)
{
PPAC_INFO_BUFFER MarshalledDelegationInfo;
PS4U_DELEGATION_INFO DelegationInfo = NULL;
S4U_DELEGATION_INFO NewDelegInfo = {0};
BYTE* FinalDelegInfoMarshalled = NULL;
ULONG FinalDelegInfoMarshalledSize = 0;
PPACTYPE OldPac;
ULONG OldPacSize;
PPACTYPE NewPac = NULL;
ULONG NewPacSize = NULL;
PUNICODE_STRING TransittedService = NULL;
UNICODE_STRING tmpstring = {0};
PUNICODE_STRING NewTargetName = NULL;
NTSTATUS Status;
KERBERR KerbErr = KDC_ERR_NONE;
OldPac = (PPACTYPE) *PacData;
OldPacSize = *PacSize;
if (PAC_UnMarshal(OldPac, OldPacSize) == 0)
{
D_DebugLog((DEB_ERROR,"Failed to unmarshal pac\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
MarshalledDelegationInfo = PAC_Find(
OldPac,
PAC_DELEGATION_INFO,
NULL
);
if ( MarshalledDelegationInfo == NULL )
{
//
// If this is using S4U, and we don't have delegation info, bomb out
// here - someone's ripped out the delegation info while we were transiting.
//
if (( S4UTicketInfo->Flags & TI_PRXY_REQUESTOR_THIS_REALM) == 0 )
{
DebugLog((DEB_ERROR, "Missing delegation info while transitting %p\n", S4UTicketInfo));
DsysAssert(FALSE);
KerbErr = KDC_ERR_PATH_NOT_ACCEPTED;
goto Cleanup;
}
//
// Time to create one.
//
if (!NT_SUCCESS(KerbDuplicateString(
&NewDelegInfo.S4U2proxyTarget,
&S4UTicketInfo->TargetName
)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
DelegationInfo = &NewDelegInfo;
D_DebugLog((DEB_T_PAC, "KdcUpdateAndValidateS4UProxyPAC create new S4uDelegateInfo: target %wZ\n", &NewDelegInfo.S4U2proxyTarget));
}
else
{
if (!NT_SUCCESS( PAC_UnmarshallS4UDelegationInfo(
&DelegationInfo,
MarshalledDelegationInfo->Data,
MarshalledDelegationInfo->cbBufferSize
)))
{
D_DebugLog((DEB_ERROR, "Failed to unmarshall S4U delgation info\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// If the target's in our realm, we need to verify that the target name
// in the PAC == the target name being requested.
//
// However, this only applies to validating the PAC in xrealm TGTs.
// If the requestor is in our realm, we need to create a targetname entry.
//
if (( S4UTicketInfo->Flags & TI_PRXY_REQUESTOR_THIS_REALM ) != 0 )
{
NewTargetName = &S4UTicketInfo->TargetName;
}
else if (( S4UTicketInfo->Flags & TI_TARGET_OUR_REALM ) != 0 )
{
if (!RtlEqualUnicodeString(
&S4UTicketInfo->TargetName,
&DelegationInfo->S4U2proxyTarget,
TRUE
))
{
D_DebugLog((DEB_ERROR, "Wrong S4UProxytarget %wZ %wZ\n", &S4UTicketInfo->TargetName, &DelegationInfo->S4U2proxyTarget));
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
goto Cleanup;
}
}
D_DebugLog((DEB_T_PAC, "KdcUpdateAndValidateS4UProxyPAC add S4uDelegateInfo: target %wZ, flags %#x\n", NewTargetName, S4UTicketInfo->Flags));
}
//
// We're in the S4U requestor's realm - add in requestor's name into
// PAC.
//
if (( S4UTicketInfo->Flags & TI_PRXY_REQUESTOR_THIS_REALM) != 0 )
{
KerbErr = KerbConvertKdcNameToString(
&tmpstring,
S4UTicketInfo->RequestorServiceName,
((S4UTicketInfo->RequestorServiceName->NameType == KRB_NT_ENTERPRISE_PRINCIPAL) && (S4UTicketInfo->RequestorServiceName->NameCount == 1))
? NULL : &S4UTicketInfo->RequestorServiceRealm
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
TransittedService = &tmpstring;
D_DebugLog((DEB_T_PAC, "KdcUpdateAndValidateS4UProxyPAC add ts %wZ\n", TransittedService));
}
#if DBG
if (DelegationInfo)
{
D_DebugLog((DEB_T_PAC, "KdcUpdateAndValidateS4UProxyPAC target %wZ\n", &DelegationInfo->S4U2proxyTarget));
for ( ULONG i = 0; i < DelegationInfo->TransitedListSize; i++ )
{
D_DebugLog((DEB_T_PAC, "KdcUpdateAndValidateS4UProxyPAC existing ts %#x: %wZ\n", i, &DelegationInfo->S4UTransitedServices[i]));
}
}
#endif // DBG
Status = PAC_InitAndUpdateTransitedService(
DelegationInfo,
TransittedService,
NewTargetName,
OldPac,
&NewPac,
&FinalDelegInfoMarshalledSize,
&FinalDelegInfoMarshalled
);
if (!NT_SUCCESS( Status ))
{
KerbErr = KRB_ERR_GENERIC;
D_DebugLog((DEB_ERROR, "PacInit&UPdatedTransitedService fail - %x\n", Status));
goto Cleanup;
}
NewPacSize = PAC_GetSize(NewPac);
if (!PAC_ReMarshal(NewPac, NewPacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
if (S4UDelegationInfo)
{
Status = UnmarshalS4UDelegationInformation(
FinalDelegInfoMarshalledSize,
FinalDelegInfoMarshalled,
S4UDelegationInfo
);
if (!NT_SUCCESS(Status))
{
KerbErr = KRB_ERR_GENERIC;
DebugLog((DEB_ERROR, "KdcUpdateAndValidateS4UProxyPAC failed to unmarshall S4U delgation info %#x\n", Status));
goto Cleanup;
}
}
if (*PacData != (PBYTE)NewPac)
{
MIDL_user_free(*PacData);
*PacData = (PBYTE) NewPac;
NewPac = NULL;
*PacSize = NewPacSize;
}
Cleanup:
KerbFreeString(&NewDelegInfo.S4U2proxyTarget);
if (( DelegationInfo != NULL ) &&
( DelegationInfo != &NewDelegInfo ))
{
MIDL_user_free( DelegationInfo );
}
if (FinalDelegInfoMarshalled != NULL)
{
MIDL_user_free(FinalDelegInfoMarshalled);
}
KerbFreeString( &tmpstring );
if (NewPac != NULL)
{
MIDL_user_free(NewPac);
}
return (KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcFilterSids
//
// Synopsis: Function that just call LsaIFilterSids. Pulled into this function
// for more widespread use than KdcCheckPacForSidFiltering.
//
// Effects:
//
// Arguments: ServerInfo structure containing attributes of the trust
// ValidationInfo authorization information to filter
//
// Requires:
//
// Returns: See LsaIFilterSids
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KdcFilterSids(
IN PKDC_TICKET_INFO ServerInfo,
IN PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo
)
{
NTSTATUS Status = STATUS_SUCCESS;
PUNICODE_STRING TrustedForest = NULL;
if ((ServerInfo->TrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0)
{
TrustedForest = &(ServerInfo->TrustedForest);
D_DebugLog((DEB_TRACE, "Filtering Sids for forest %wZ\n", TrustedForest));
}
if ( ServerInfo->TrustSid != NULL ||
ServerInfo->TrustType == TRUST_TYPE_MIT ||
( ServerInfo->TrustAttributes & TRUST_ATTRIBUTE_CROSS_ORGANIZATION ) != 0 )
{
Status = LsaIFilterSids(
TrustedForest, // Pass domain name here
TRUST_DIRECTION_OUTBOUND,
ServerInfo->TrustType,
ServerInfo->TrustAttributes,
ServerInfo->TrustSid,
NetlogonValidationSamInfo2,
ValidationInfo,
NULL,
NULL,
NULL
);
if (!NT_SUCCESS(Status))
{
//
// Create an audit log if it looks like the SID has been tampered with
//
if ((STATUS_DOMAIN_TRUST_INCONSISTENT == Status) &&
SecData.AuditKdcEvent(KDC_AUDIT_TGS_FAILURE))
{
DWORD Dummy = 0;
KdcLsaIAuditTgsEvent(
SE_AUDITID_TGS_TICKET_REQUEST,
&ValidationInfo->EffectiveName,
&ValidationInfo->LogonDomainName,
NULL,
&ServerInfo->AccountName,
NULL,
&Dummy,
(PULONG) &Status,
NULL,
NULL, // no preauth type
GET_CLIENT_ADDRESS(NULL),
NULL, // no logon guid
NULL
);
}
DebugLog((DEB_ERROR,"Failed to filter SIDS (LsaIFilterSids): 0x%x\n",Status));
}
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KdcFilterNamespace
//
// Synopsis: Function that just call lsaifiltersids. Pulled into this function
// for more widespread use than KdcCheckPacForSidFiltering.
//
// Effects:
//
// Arguments: ServerInfo structure containing attributes of the trust
// ClientRealm namespace to filter
//
// Requires:
//
// Returns: KDC_ERR_NONE namespace is good to go
// KDC_ERR_POLICY filtering policy rejects this namespace
// KDC_ERR_GENERIC unexpected error (out of memory, etc)
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcFilterNamespace(
IN PKDC_TICKET_INFO ServerInfo,
IN KERB_REALM ClientRealm,
OUT PKERB_EXT_ERROR pExtendedError
)
{
NTSTATUS Status;
KERBERR KerbErr;
UNICODE_STRING ClientRealmU = {0};
if ( ServerInfo == NULL ||
ServerInfo->TrustType == 0 ||
( ServerInfo->TrustSid == NULL && ServerInfo->TrustType != TRUST_TYPE_MIT ))
{
//
// Not going over a trust, simply succeed
//
return KDC_ERR_NONE;
}
//
// We can only digest Unicode strings below
//
KerbErr = KerbConvertRealmToUnicodeString(
&ClientRealmU,
&ClientRealm
);
if ( !KERB_SUCCESS( KerbErr ))
{
return KerbErr;
}
//
// Let LSA policy logic decide what's kosher
//
Status = LsaIFilterNamespace(
&ServerInfo->AccountName, // misnomer - contains DNS domain name
TRUST_DIRECTION_OUTBOUND,
ServerInfo->TrustType,
ServerInfo->TrustAttributes,
&ClientRealmU
);
KerbFreeString( &ClientRealmU );
switch ( Status )
{
case STATUS_SUCCESS:
return KDC_ERR_NONE;
case STATUS_DOMAIN_TRUST_INCONSISTENT:
FILL_EXT_ERROR_EX2( pExtendedError, STATUS_DOMAIN_TRUST_INCONSISTENT, FILENO, __LINE__ );
return KDC_ERR_POLICY;
case STATUS_INSUFFICIENT_RESOURCES:
default:
FILL_EXT_ERROR( pExtendedError, Status, FILENO, __LINE__ );
return KRB_ERR_GENERIC;
}
}
//+-------------------------------------------------------------------------
//
// Function: KdcCheckPacForSidFiltering
//
// Synopsis: If the server ticket info has a TDOSid then the function
// makes a check to make sure the SID from the TDO matches
// the client's home domain SID. A call to LsaIFilterSids
// is made to do the check. If this function fails with
// STATUS_TRUST_FAILURE then an audit log is generated.
// Otherwise the function succeeds but SIDs are filtered
// from the PAC.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcCheckPacForSidFiltering(
IN PKDC_TICKET_INFO ServerInfo,
IN OUT PUCHAR *PacData,
IN OUT PULONG PacSize
)
{
NTSTATUS Status;
KERBERR KerbErr = KDC_ERR_NONE;
PPAC_INFO_BUFFER LogonInfo;
PPACTYPE OldPac;
ULONG OldPacSize;
PPACTYPE NewPac = NULL;
PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo = NULL;
SAMPR_PSID_ARRAY ZeroResourceGroups;
ULONG OldExtraSidCount;
PNETLOGON_SID_AND_ATTRIBUTES SavedExtraSids = NULL;
PPACTYPE RemarshalPac = NULL;
ULONG RemarshalPacSize = 0;
OldPac = (PPACTYPE) *PacData;
OldPacSize = *PacSize;
if (PAC_UnMarshal(OldPac, OldPacSize) == 0)
{
D_DebugLog((DEB_ERROR,"Failed to unmarshal pac\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Must remember to remarshal the PAC prior to returning
//
RemarshalPac = OldPac;
RemarshalPacSize = OldPacSize;
RtlZeroMemory(
&ZeroResourceGroups,
sizeof(ZeroResourceGroups)); // allows us to use PAC_InitAndUpdateGroups to remarshal the PAC
//
// First, find the logon information
//
LogonInfo = PAC_Find(
OldPac,
PAC_LOGON_INFO,
NULL
);
if (LogonInfo == NULL)
{
D_DebugLog((DEB_WARN,"No logon info for PAC - not making SID filtering check\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Now unmarshall the validation information and build a list of sids
//
if (!NT_SUCCESS(PAC_UnmarshallValidationInfo(
&ValidationInfo,
LogonInfo->Data,
LogonInfo->cbBufferSize)))
{
D_DebugLog((DEB_ERROR,"Failed to unmarshall validation info!\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Save the old extra SID count (so that if KdcFilterSids compresses
// the SID array, we can avoid allocating memory for the other-org SID later)
//
OldExtraSidCount = ValidationInfo->SidCount;
//
// Call lsaifiltersids().
//
Status = KdcFilterSids(
ServerInfo,
ValidationInfo
);
if (!NT_SUCCESS(Status))
{
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
//
// If we're crossing an organization boundary, add the "other organization"
// SID to the PAC.
//
// NOTE: for efficiency reasons, no check is made for whether the
// SID is already in the PAC. The hope is that adding a duplicate
// SID will not cause problems.
//
if ( ServerInfo->TrustAttributes & TRUST_ATTRIBUTE_CROSS_ORGANIZATION )
{
if ( ValidationInfo->SidCount >= OldExtraSidCount )
{
SavedExtraSids = ValidationInfo->ExtraSids;
SafeAllocaAllocate(
ValidationInfo->ExtraSids,
sizeof( SID_AND_ATTRIBUTES ) * ( ValidationInfo->SidCount + 1 )
);
if ( ValidationInfo->ExtraSids == NULL )
{
ValidationInfo->ExtraSids = SavedExtraSids;
SavedExtraSids = NULL;
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlCopyMemory(
ValidationInfo->ExtraSids,
SavedExtraSids,
sizeof( SID_AND_ATTRIBUTES ) * ValidationInfo->SidCount
);
}
ValidationInfo->ExtraSids[ValidationInfo->SidCount].Sid =
GlobalOtherOrganizationSid;
ValidationInfo->ExtraSids[ValidationInfo->SidCount].Attributes =
SE_GROUP_MANDATORY |
SE_GROUP_ENABLED_BY_DEFAULT |
SE_GROUP_ENABLED;
ValidationInfo->SidCount += 1;
}
//
// Now build a new pac
//
Status = PAC_InitAndUpdateGroups(
ValidationInfo,
&ZeroResourceGroups,
OldPac,
&NewPac
);
if (!NT_SUCCESS(Status))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RemarshalPacSize = PAC_GetSize(NewPac);
RemarshalPac = NewPac;
Cleanup:
if ( RemarshalPac != NULL )
{
if (!PAC_ReMarshal(RemarshalPac, RemarshalPacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
}
else if ( NewPac != NULL &&
*PacData != (PBYTE)NewPac )
{
MIDL_user_free(*PacData);
*PacData = (PBYTE) NewPac;
NewPac = NULL;
*PacSize = RemarshalPacSize;
}
}
if (NewPac != NULL)
{
MIDL_user_free(NewPac);
}
if ( SavedExtraSids != NULL )
{
SafeAllocaFree( ValidationInfo->ExtraSids );
ValidationInfo->ExtraSids = SavedExtraSids;
ValidationInfo->SidCount -= 1;
}
if (ValidationInfo != NULL)
{
MIDL_user_free(ValidationInfo);
}
return(KerbErr);
}
#ifdef ROGUE_DC
#pragma message( "COMPILING A ROGUE DC!!!" )
#pragma message( "MUST NOT SHIP THIS BUILD!!!" )
extern HKEY hKdcRogueKey;
KERBERR
KdcInstrumentRoguePac(
IN OUT PKERB_AUTHORIZATION_DATA PacAuthData
)
{
KERBERR KerbErr;
NTSTATUS Status;
PNETLOGON_VALIDATION_SAM_INFO3 OldValidationInfo = NULL;
NETLOGON_VALIDATION_SAM_INFO3 NewValidationInfo = {0};
SAMPR_PSID_ARRAY ZeroResourceGroups = {0};
PPACTYPE NewPac = NULL;
ULONG NewPacSize;
PPAC_INFO_BUFFER LogonInfo;
PSID LogonDomainId = NULL;
PSID ResourceGroupDomainSid = NULL;
PGROUP_MEMBERSHIP GroupIds = NULL;
PGROUP_MEMBERSHIP ResourceGroupIds = NULL;
PNETLOGON_SID_AND_ATTRIBUTES ExtraSids = NULL;
BYTE FullUserSidBuffer[MAX_SID_LEN];
SID * FullUserSid = ( SID * )FullUserSidBuffer;
CHAR * FullUserSidText = NULL;
DWORD dwType;
DWORD cbData = 0;
PCHAR Buffer;
PCHAR Value = NULL;
BOOLEAN PacChanged = FALSE;
//
// Optimization: no "rogue" key in registry - nothing for us to do
//
if ( hKdcRogueKey == NULL )
{
return STATUS_SUCCESS;
}
//
// Unmarshall the old PAC
//
if ( PAC_UnMarshal(
(PPACTYPE)PacAuthData->value.auth_data.value,
PacAuthData->value.auth_data.length) == 0 )
{
DebugLog((DEB_ERROR, "ROGUE: Unable to unmarshal the PAC\n"));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// First, find the logon information
//
LogonInfo = PAC_Find(
(PPACTYPE)PacAuthData->value.auth_data.value,
PAC_LOGON_INFO,
NULL
);
if ( LogonInfo == NULL )
{
DebugLog((DEB_ERROR, "ROGUE: No logon info on PAC - not performing substitution\n"));
KerbErr = KDC_ERR_NONE;
goto Error;
}
//
// Now unmarshall the validation information and build a list of sids
//
if ( !NT_SUCCESS(PAC_UnmarshallValidationInfo(
&OldValidationInfo,
LogonInfo->Data,
LogonInfo->cbBufferSize )))
{
DebugLog((DEB_ERROR, "ROGUE: Unable to unmarshal validation info\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
//
// Construct the text form of the full user's SID (logon domain ID + user ID)
//
DsysAssert( sizeof( FullUserSidBuffer ) >= MAX_SID_LEN );
RtlCopySid(
sizeof( FullUserSidBuffer ),
FullUserSid,
OldValidationInfo->LogonDomainId
);
FullUserSid->SubAuthority[FullUserSid->SubAuthorityCount] = OldValidationInfo->UserId;
FullUserSid->SubAuthorityCount += 1;
if ( FALSE == ConvertSidToStringSidA(
FullUserSid,
&FullUserSidText ))
{
DebugLog((DEB_ERROR, "ROGUE: Unable to convert user's SID\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
//
// Now look in the registry for the SID matching the validation info
//
if ( ERROR_SUCCESS != RegQueryValueExA(
hKdcRogueKey,
FullUserSidText,
NULL,
&dwType,
NULL,
&cbData ) ||
dwType != REG_MULTI_SZ ||
cbData <= 1 )
{
DebugLog((DEB_ERROR, "ROGUE: No substitution info available for %s\n", FullUserSidText));
KerbErr = KDC_ERR_NONE;
goto Error;
}
SafeAllocaAllocate( Value, cbData );
if ( Value == NULL )
{
DebugLog((DEB_ERROR, "ROGUE: Out of memory allocating substitution buffer\n", FullUserSidText));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
if ( ERROR_SUCCESS != RegQueryValueExA(
hKdcRogueKey,
FullUserSidText,
NULL,
&dwType,
(PBYTE)Value,
&cbData ) ||
dwType != REG_MULTI_SZ ||
cbData <= 1 )
{
DebugLog((DEB_ERROR, "ROGUE: Error reading from registry\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
DebugLog((DEB_ERROR, "ROGUE: Substituting the PAC for %s\n", FullUserSidText));
if ( _stricmp( Value, "xPAC" ) == 0 )
{
//
// This means that the logon info should be stripped from the PAC
//
Status = PAC_RemoveSection(
(PPACTYPE)PacAuthData->value.auth_data.value,
PAC_LOGON_INFO,
&NewPac
);
if ( NT_SUCCESS( Status ))
{
NewPacSize = PAC_GetSize( NewPac );
if (!PAC_ReMarshal(NewPac, NewPacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
MIDL_user_free( PacAuthData->value.auth_data.value );
PacAuthData->value.auth_data.value = (PBYTE) NewPac;
NewPac = NULL;
PacAuthData->value.auth_data.length = NewPacSize;
}
else
{
DebugLog((DEB_ERROR, "ROGUE: Unable to strip PAC_LOGON_INFO\n"));
}
KerbErr = KDC_ERR_NONE;
goto Cleanup;
}
Buffer = Value;
//
// New validation info will be overloaded with stuff from the file
//
NewValidationInfo = *OldValidationInfo;
//
// Read the input file one line at a time
//
while ( *Buffer != '\0' )
{
switch( Buffer[0] )
{
case 'l':
case 'L': // logon domain ID
if ( LogonDomainId != NULL )
{
DebugLog((DEB_ERROR, "ROGUE: Logon domain ID specified more than once - only first one kept\n"));
break;
}
DebugLog((DEB_ERROR, "ROGUE: Substituting logon domain ID by %s\n", &Buffer[1]));
if ( FALSE == ConvertStringSidToSidA(
&Buffer[1],
&LogonDomainId ))
{
DebugLog((DEB_ERROR, "ROGUE: Unable to convert SID\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
if ( LogonDomainId == NULL )
{
DebugLog((DEB_ERROR, "ROGUE: Out of memory allocating LogonDomainId\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
NewValidationInfo.LogonDomainId = LogonDomainId;
LogonDomainId = NULL;
PacChanged = TRUE;
break;
case 'd':
case 'D': // resource group domain SID
if ( ResourceGroupDomainSid != NULL )
{
DebugLog((DEB_ERROR, "ROGUE: Resource group domain SID specified more than once - only first one kept\n"));
break;
}
DebugLog((DEB_ERROR, "ROGUE: Substituting resource group domain SID by %s\n", &Buffer[1]));
if ( FALSE == ConvertStringSidToSidA(
&Buffer[1],
&ResourceGroupDomainSid ))
{
DebugLog((DEB_ERROR, "ROGUE: Unable to convert SID\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
if ( ResourceGroupDomainSid == NULL )
{
DebugLog((DEB_ERROR, "ROGUE: Out of memory allocating ResourceGroupDomainSid\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
NewValidationInfo.ResourceGroupDomainSid = ResourceGroupDomainSid;
ResourceGroupDomainSid = NULL;
PacChanged = TRUE;
break;
case 'p':
case 'P': // primary group ID
DebugLog((DEB_ERROR, "ROGUE: Substituting primary group ID by %s\n", &Buffer[1]));
NewValidationInfo.PrimaryGroupId = atoi(&Buffer[1]);
PacChanged = TRUE;
break;
case 'u':
case 'U': // User ID
DebugLog((DEB_ERROR, "ROGUE: Substituting user ID by %s\n", &Buffer[1]));
NewValidationInfo.UserId = atoi(&Buffer[1]);
PacChanged = TRUE;
break;
case 'e':
case 'E': // Extra SID
DebugLog((DEB_ERROR, "ROGUE: Adding an ExtraSid: %s\n", &Buffer[1]));
if ( ExtraSids == NULL )
{
NewValidationInfo.ExtraSids = NULL;
NewValidationInfo.SidCount = 0;
ExtraSids = ( PNETLOGON_SID_AND_ATTRIBUTES )HeapAlloc(
GetProcessHeap(),
0,
sizeof( NETLOGON_SID_AND_ATTRIBUTES )
);
}
else
{
ExtraSids = ( PNETLOGON_SID_AND_ATTRIBUTES )HeapReAlloc(
GetProcessHeap(),
0,
NewValidationInfo.ExtraSids,
( NewValidationInfo.SidCount + 1 ) * sizeof( NETLOGON_SID_AND_ATTRIBUTES )
);
}
if ( ExtraSids == NULL )
{
DebugLog((DEB_ERROR, "ROGUE: Out of memory allocating ExtraSids\n"));
ExtraSids = NewValidationInfo.ExtraSids;
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
//
// Read the actual SID
//
NewValidationInfo.ExtraSids = ExtraSids;
if ( FALSE == ConvertStringSidToSidA(
&Buffer[1],
&NewValidationInfo.ExtraSids[NewValidationInfo.SidCount].Sid ))
{
DebugLog((DEB_ERROR, "ROGUE: Unable to convert SID\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
if ( NewValidationInfo.ExtraSids[NewValidationInfo.SidCount].Sid == NULL )
{
DebugLog((DEB_ERROR, "ROGUE: Out of memory allocating an extra SID\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
NewValidationInfo.ExtraSids[NewValidationInfo.SidCount].Attributes =
SE_GROUP_MANDATORY |
SE_GROUP_ENABLED_BY_DEFAULT |
SE_GROUP_ENABLED;
NewValidationInfo.SidCount += 1;
PacChanged = TRUE;
break;
case 'g':
case 'G': // Group ID
DebugLog((DEB_ERROR, "ROGUE: Adding a GroupId: %s\n", &Buffer[1]));
if ( GroupIds == NULL )
{
NewValidationInfo.GroupIds = NULL;
NewValidationInfo.GroupCount = 0;
GroupIds = ( PGROUP_MEMBERSHIP )HeapAlloc(
GetProcessHeap(),
0,
sizeof( GROUP_MEMBERSHIP )
);
}
else
{
GroupIds = ( PGROUP_MEMBERSHIP )HeapReAlloc(
GetProcessHeap(),
0,
NewValidationInfo.GroupIds,
( NewValidationInfo.GroupCount + 1 ) * sizeof( GROUP_MEMBERSHIP )
);
}
if ( GroupIds == NULL )
{
DebugLog((DEB_ERROR, "ROGUE: Out of memory allocating Group IDs\n"));
GroupIds = NewValidationInfo.GroupIds;
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
//
// Read the actual ID
//
NewValidationInfo.GroupIds = GroupIds;
NewValidationInfo.GroupIds[NewValidationInfo.GroupCount].RelativeId = atoi(&Buffer[1]);
NewValidationInfo.GroupIds[NewValidationInfo.GroupCount].Attributes =
SE_GROUP_MANDATORY |
SE_GROUP_ENABLED_BY_DEFAULT |
SE_GROUP_ENABLED;
NewValidationInfo.GroupCount += 1;
PacChanged = TRUE;
break;
case 'r':
case 'R': // Resource groups
DebugLog((DEB_ERROR, "ROGUE: Adding a ResourceGroupId: %s\n", &Buffer[1]));
if ( ResourceGroupIds == NULL )
{
NewValidationInfo.ResourceGroupIds = NULL;
NewValidationInfo.ResourceGroupCount = 0;
ResourceGroupIds = ( PGROUP_MEMBERSHIP )HeapAlloc(
GetProcessHeap(),
0,
sizeof( GROUP_MEMBERSHIP )
);
}
else
{
ResourceGroupIds = ( PGROUP_MEMBERSHIP )HeapReAlloc(
GetProcessHeap(),
0,
NewValidationInfo.ResourceGroupIds,
( NewValidationInfo.ResourceGroupCount + 1 ) * sizeof( GROUP_MEMBERSHIP )
);
}
if ( ResourceGroupIds == NULL )
{
DebugLog((DEB_ERROR, "ROGUE: Out of memory allocating Resource Group IDs\n"));
ResourceGroupIds = NewValidationInfo.ResourceGroupIds;
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
//
// Read the actual ID
//
NewValidationInfo.ResourceGroupIds = ResourceGroupIds;
NewValidationInfo.ResourceGroupIds[NewValidationInfo.ResourceGroupCount].RelativeId = atoi(&Buffer[1]);
NewValidationInfo.ResourceGroupIds[NewValidationInfo.ResourceGroupCount].Attributes =
SE_GROUP_MANDATORY |
SE_GROUP_ENABLED_BY_DEFAULT |
SE_GROUP_ENABLED;
NewValidationInfo.ResourceGroupCount += 1;
PacChanged = TRUE;
break;
default: // unrecognized
DebugLog((DEB_ERROR, "ROGUE: Entry \'%c\' unrecognized\n", Buffer[0]));
break;
}
//
// Move to the next line
//
while (*Buffer++ != '\0');
}
if ( !PacChanged )
{
DebugLog((DEB_ERROR, "ROGUE: Nothing to substitute for %s\n", FullUserSidText));
KerbErr = KDC_ERR_NONE;
goto Error;
}
//
// If resource group IDs were added, indicate that by setting the corresponding flag
//
if ( ResourceGroupIds )
{
NewValidationInfo.UserFlags |= LOGON_RESOURCE_GROUPS;
}
//
// If extra SIDs were added, indicate that by setting the corresponding flag
//
if ( ExtraSids )
{
NewValidationInfo.UserFlags |= LOGON_EXTRA_SIDS;
}
//
// Now build a new pac
//
Status = PAC_InitAndUpdateGroups(
&NewValidationInfo,
&ZeroResourceGroups,
(PPACTYPE)PacAuthData->value.auth_data.value,
&NewPac
);
if ( !NT_SUCCESS( Status ))
{
DebugLog((DEB_ERROR, "ROGUE: Error 0x%x from PAC_InitAndUpdateGroups\n"));
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
NewPacSize = PAC_GetSize( NewPac );
if (!PAC_ReMarshal(NewPac, NewPacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
goto Error;
}
MIDL_user_free( PacAuthData->value.auth_data.value );
PacAuthData->value.auth_data.value = (PBYTE) NewPac;
NewPac = NULL;
PacAuthData->value.auth_data.length = NewPacSize;
KerbErr = KDC_ERR_NONE;
Cleanup:
MIDL_user_free( OldValidationInfo );
LocalFree( FullUserSidText );
LocalFree( ResourceGroupDomainSid );
LocalFree( LogonDomainId );
HeapFree( GetProcessHeap(), 0, ResourceGroupIds );
HeapFree( GetProcessHeap(), 0, GroupIds );
if ( ExtraSids )
{
for ( ULONG i = 0; i < NewValidationInfo.SidCount; i++ )
{
HeapFree( GetProcessHeap(), 0, ExtraSids[i].Sid );
}
HeapFree( GetProcessHeap(), 0, ExtraSids );
}
MIDL_user_free( NewPac );
SafeAllocaFree( Value );
return KerbErr;
Error:
if ( !KERB_SUCCESS( KerbErr ))
{
DebugLog((DEB_ERROR, "ROGUE: Substitution encountered an error, not performed\n"));
}
if ( !PAC_ReMarshal(
(PPACTYPE)PacAuthData->value.auth_data.value,
PacAuthData->value.auth_data.length ))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
}
goto Cleanup;
}
#endif
//+-------------------------------------------------------------------------
//
// Function: KdcVerifyAndResignPac
//
// Synopsis: Verifies the signature on a PAC and re-signs it with the
// new servers & kdc's key
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcCheckForDelegationInfo(
IN OUT PUCHAR PacData,
IN OUT ULONG PacSize,
IN OUT PBOOLEAN InfoPresent
)
{
KERBERR KerbErr = KDC_ERR_NONE;
PPAC_INFO_BUFFER DelegationInfo = NULL;
PPACTYPE Pac;
ULONG Size;
*InfoPresent = FALSE;
Pac = (PPACTYPE) PacData;
Size = PacSize;
if (PAC_UnMarshal(Pac, Size) == 0)
{
D_DebugLog((DEB_ERROR,"Failed to unmarshal pac\n"));
return KRB_ERR_GENERIC;
}
DelegationInfo = PAC_Find(
Pac,
PAC_DELEGATION_INFO,
NULL
);
if (!PAC_ReMarshal(Pac, Size))
{
DsysAssert(!"PAC_Remarshal Failed");
return KRB_ERR_GENERIC;
}
*InfoPresent = (DelegationInfo != NULL);
return KerbErr;
}
//+-------------------------------------------------------------------------
//
// Function: KdcVerifyAndResignPac
//
// Synopsis: Verifies the signature on a PAC and re-signs it with the
// new servers & kdc's key
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcVerifyAndResignPac(
IN PKERB_ENCRYPTION_KEY OldKey,
IN PKERB_ENCRYPTION_KEY NewKey,
IN PKDC_TICKET_INFO OldServerInfo,
IN OPTIONAL PKDC_TICKET_INFO TargetServiceInfo,
IN OPTIONAL PKDC_S4U_TICKET_INFO S4UTicketInfo,
IN OPTIONAL PKERB_ENCRYPTED_TICKET FinalTicket,
IN BOOLEAN AddResourceGroups,
IN PKERB_EXT_ERROR ExtendedError,
IN OUT PKERB_AUTHORIZATION_DATA PacAuthData,
OUT OPTIONAL PS4U_DELEGATION_INFO* S4UDelegationInfo
)
{
KERBERR KerbErr = KDC_ERR_NONE;
BOOLEAN InfoPresent = FALSE;
BOOLEAN DCTarget = FALSE;
TRACE(KDC, KdcVerifyAndResignPac, DEB_FUNCTION);
PKDC_TICKET_INFO LocalServerInfo = OldServerInfo;
//
// If the TI_PRXY_REQUESTOR_THIS_REALM bit is set, then
// the evidence ticket is encrypted in the requestor's key.
//
if (( ARGUMENT_PRESENT( S4UTicketInfo ) ) &&
( S4UTicketInfo->Flags & TI_S4UPROXY_INFO ))
{
LocalServerInfo = &S4UTicketInfo->RequestorTicketInfo;
}
if (ARGUMENT_PRESENT( TargetServiceInfo ))
{
DCTarget = ((TargetServiceInfo->UserAccountControl & USER_SERVER_TRUST_ACCOUNT ) != 0);
}
//
// Delegation info in PAC? Then it better not be interdomain, as
// constrained delegation (S4UProxy) only works in a single
// domain. Reject the request.
//
if ( OldServerInfo->UserId == DOMAIN_USER_RID_KRBTGT &&
( OldServerInfo->UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT ))
{
KerbErr = KdcCheckForDelegationInfo(
PacAuthData->value.auth_data.value,
PacAuthData->value.auth_data.length,
&InfoPresent
);
if ( InfoPresent )
{
DebugLog((DEB_ERROR, "Attempting XRealm S4UProxy Inbound\n"));
KerbErr = KDC_ERR_POLICY;
FILL_EXT_ERROR_EX2( ExtendedError, STATUS_CROSSREALM_DELEGATION_FAILURE, FILENO, __LINE__ );
goto Cleanup;
}
else if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
}
//
// Now verify the existing signature
//
KerbErr = KdcVerifyPacSignature(
OldKey,
LocalServerInfo,
PacAuthData->value.auth_data.length,
PacAuthData->value.auth_data.value
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Perform SID filtering if necessary
//
KerbErr = KdcCheckPacForSidFiltering(
OldServerInfo,
&PacAuthData->value.auth_data.value,
(PULONG) &PacAuthData->value.auth_data.length
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// See if this is an S4U variant, and fill in the interesting pac sections.
//
if (ARGUMENT_PRESENT( S4UTicketInfo ))
{
if (( S4UTicketInfo->Flags & TI_S4UPROXY_INFO ) != 0)
{
//
// S4UProxy
// 2 courses of action here.
// 1. If this is for a server in our realm, then we need to insert the
// target into the S4U_DELEGATION_INFO.
// 2. If we're transitting, validate the target name vs. what's in the PAC,
// and continue on.
//
KerbErr = KdcUpdateAndValidateS4UProxyPAC(
S4UTicketInfo,
&PacAuthData->value.auth_data.value,
(PULONG) &PacAuthData->value.auth_data.length,
S4UDelegationInfo
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR, "KdcUpdateAndValidateS4UProxyInfo failed - %x\n", KerbErr));
goto Cleanup;
}
}
else if (( S4UTicketInfo->Flags & TI_REQUESTOR_THIS_REALM ) != 0)
{
//
// S4USelf - replace the pac verifier w/ one that is acceptable to the
// destination server.
//
KerbErr = KdcUpdateAndVerifyS4UPacVerifier(
S4UTicketInfo,
&PacAuthData->value.auth_data.value,
(PULONG) &PacAuthData->value.auth_data.length
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR, "KdcVerifyS4UPacVerifier failed - %x\n", KerbErr));
goto Cleanup;
}
}
}
#ifdef ROGUE_DC
KerbErr = KdcInstrumentRoguePac( PacAuthData );
if ( !KERB_SUCCESS( KerbErr ))
{
DebugLog((DEB_ERROR, "KdcInstrumentRoguePac failed\n"));
}
#endif
//
// Now resign the PAC. If we add new sig algs, then we may need to
// address growing sigs, but for now, its all KDC_PAC_CHECKSUM
//
KerbErr = KdcSignPac(
NewKey,
AddResourceGroups,
DCTarget,
&PacAuthData->value.auth_data.value,
(PULONG) &PacAuthData->value.auth_data.length
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
Cleanup:
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcFreeAuthzInfo
//
// Synopsis: Used to free buffers / handles from KdcGetValidationInfoFromTgt.
// Allows us to use allocated buffers w/o copy overhead.
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KdcFreeAuthzInfo(
IN PKDC_AUTHZ_GROUP_BUFFERS InfoToFree
)
{
if (InfoToFree->BuiltInSids)
{
MIDL_user_free(InfoToFree->BuiltInSids);
}
if (InfoToFree->PacGroups.Sids != NULL)
{
for (ULONG Index = 0; Index < InfoToFree->PacGroups.Count ;Index++ )
{
if (InfoToFree->PacGroups.Sids[Index].SidPointer != NULL)
{
MIDL_user_free(InfoToFree->PacGroups.Sids[Index].SidPointer);
}
}
MIDL_user_free(InfoToFree->PacGroups.Sids);
}
SamIFree_SAMPR_ULONG_ARRAY( &InfoToFree->AliasGroups );
SamIFreeSidArray( InfoToFree->ResourceGroups );
if ( InfoToFree->SidAndAttributes )
{
MIDL_user_free(InfoToFree->SidAndAttributes);
}
if ( InfoToFree->ValidationInfo )
{
MIDL_user_free( InfoToFree->ValidationInfo );
}
RtlZeroMemory(
InfoToFree,
sizeof(KDC_AUTHZ_GROUP_BUFFERS)
);
}
//+-------------------------------------------------------------------------
//
// Function: KdcGetSidsFromTgt
//
// Synopsis: Takes a TGT, grabs the PAC from the authorization data, and
// extracts the validation info, builds groups up.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcGetSidsFromTgt(
IN PKERB_ENCRYPTED_TICKET EncryptedTicket,
IN OPTIONAL PKERB_ENCRYPTION_KEY EncryptedTicketKey,
IN ULONG EncryptionType,
IN PKDC_TICKET_INFO TgtAccountInfo,
IN OUT PKDC_AUTHZ_INFO AuthzInfo,
IN OUT PKDC_AUTHZ_GROUP_BUFFERS InfoToFree,
OUT NTSTATUS * pStatus
)
{
KERBERR KerbErr;
NTSTATUS Status = STATUS_SUCCESS;
PKERB_AUTHORIZATION_DATA SourceAuthData = NULL;
PKERB_IF_RELEVANT_AUTH_DATA * IfRelevantData = NULL;
PKERB_AUTHORIZATION_DATA PacAuthData = NULL;
PKERB_ENCRYPTION_KEY KdcKey = EncryptedTicketKey;
PPAC_INFO_BUFFER LogonInfo;
PPACTYPE Pac;
ULONG PacSize;
PNETLOGON_VALIDATION_SAM_INFO3 LocalValidationInfo = NULL;
SAMPR_PSID_ARRAY SidList = {0};
PSAMPR_PSID_ARRAY ResourceGroups = NULL;
SAMPR_ULONG_ARRAY BuiltinGroups = {0, NULL};
PSID SidBuffer = NULL;
PNETLOGON_SID_AND_ATTRIBUTES SidAndAttributes = NULL;
ULONG Index, Index2, GroupCount = 0, SidSize = 0;
RtlZeroMemory(
InfoToFree,
sizeof(KDC_AUTHZ_GROUP_BUFFERS)
);
if (EncryptedTicket->bit_mask & KERB_ENCRYPTED_TICKET_authorization_data_present)
{
DsysAssert(EncryptedTicket->KERB_ENCRYPTED_TICKET_authorization_data != NULL);
SourceAuthData = EncryptedTicket->KERB_ENCRYPTED_TICKET_authorization_data;
}
else
{
DsysAssert(FALSE);
*pStatus = STATUS_INVALID_PARAMETER;
return KRB_ERR_GENERIC;
}
KerbErr = KerbGetPacFromAuthData(
SourceAuthData,
&IfRelevantData,
&PacAuthData
);
if ( PacAuthData == NULL )
{
KerbErr = KRB_ERR_GENERIC;
}
if (!KERB_SUCCESS(KerbErr))
{
Status = STATUS_NO_MEMORY; // difficult to figure out exactly what it was
goto Cleanup;
}
//
// Verify the signature, using Krbtgt key.
//
if ( KdcKey == NULL )
{
KdcKey = KerbGetKeyFromList(
TgtAccountInfo->Passwords,
EncryptionType
);
if ( KdcKey == NULL )
{
DebugLog((DEB_ERROR, "Can't find key for PAC (%x)\n", EncryptionType ));
KerbErr = KRB_ERR_GENERIC;
Status = STATUS_NO_KERB_KEY;
goto Cleanup;
}
}
KerbErr = KdcVerifyPacSignature(
KdcKey,
TgtAccountInfo,
PacAuthData->value.auth_data.length,
PacAuthData->value.auth_data.value
);
if (!KERB_SUCCESS( KerbErr ))
{
DebugLog((DEB_ERROR,"PAC signature didn't verify\n"));
Status = KerbMapKerbError( KerbErr );
goto Cleanup;
}
Pac = (PPACTYPE) PacAuthData->value.auth_data.value;
PacSize = PacAuthData->value.auth_data.length;
if (PAC_UnMarshal(Pac, PacSize) == 0)
{
D_DebugLog((DEB_ERROR,"Failed to unmarshal pac\n"));
KerbErr = KRB_ERR_GENERIC;
Status = STATUS_INVALID_PARAMETER; // better error code?
goto Cleanup;
}
LogonInfo = PAC_Find(
Pac,
PAC_LOGON_INFO,
NULL
);
if ( LogonInfo == NULL )
{
KerbErr = KRB_ERR_GENERIC;
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
Status = PAC_UnmarshallValidationInfo(
&LocalValidationInfo,
LogonInfo->Data,
LogonInfo->cbBufferSize
);
if ( !NT_SUCCESS( Status ))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Filter the sids. If trust sid is present, this is an inbound TGT. We
// don't do this for TGTs from our own domain.
//
if (NULL != TgtAccountInfo->TrustSid)
{
Status = KdcFilterSids(
TgtAccountInfo,
LocalValidationInfo
);
if ( !NT_SUCCESS( Status ))
{
KerbErr = KDC_ERR_POLICY;
goto Cleanup;
}
}
//
// Make a sid list from the validation info
//
KerbErr = KdcBuildPacSidList(
LocalValidationInfo,
TRUE, // Add everyone and authenticated user sids.
(( TgtAccountInfo->TrustAttributes & TRUST_ATTRIBUTE_CROSS_ORGANIZATION ) != 0 ),
&SidList
);
if (!KERB_SUCCESS(KerbErr))
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory(
&InfoToFree->PacGroups,
&SidList,
sizeof(SAMPR_PSID_ARRAY)
);
//
// Call SAM to get the sids for resource groups and built-in groups
//
Status = SamIGetResourceGroupMembershipsTransitive(
GlobalAccountDomainHandle,
&SidList,
0, // no flags
&ResourceGroups
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to get resource groups: 0x%x\n",Status));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
InfoToFree->ResourceGroups = ResourceGroups;
Status = SamIGetAliasMembership(
GlobalBuiltInDomainHandle,
&SidList,
&BuiltinGroups
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to get ALIAS MEMBERSHIP groups: 0x%x\n",Status));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlCopyMemory(
&InfoToFree->AliasGroups,
&BuiltinGroups,
sizeof(SAMPR_ULONG_ARRAY)
);
GroupCount = BuiltinGroups.Count + ResourceGroups->Count + SidList.Count;
//
// Enumerate and allocate the groups sids...
//
if (GroupCount != 0)
{
SidAndAttributes = (PNETLOGON_SID_AND_ATTRIBUTES) MIDL_user_allocate(sizeof(NETLOGON_SID_AND_ATTRIBUTES) * GroupCount);
if (SidAndAttributes == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
InfoToFree->SidAndAttributes = SidAndAttributes;
//
// Add in all the extra sids that are not resource groups
//
Index2 = 0;
for (Index = 0; Index < SidList.Count; Index++ )
{
SidAndAttributes[Index2].Sid = SidList.Sids[Index].SidPointer;
SidAndAttributes[Index2].Attributes = SE_GROUP_MANDATORY |
SE_GROUP_ENABLED |
SE_GROUP_ENABLED_BY_DEFAULT;
Index2++;
}
//
// Copy all the resource group SIDs
//
for (Index = 0; Index < ResourceGroups->Count ; Index++ )
{
SidAndAttributes[Index2].Sid = ResourceGroups->Sids[Index].SidPointer;
SidAndAttributes[Index2].Attributes = SE_GROUP_MANDATORY |
SE_GROUP_ENABLED |
SE_GROUP_ENABLED_BY_DEFAULT |
SE_GROUP_RESOURCE;
Index2++;
}
//
// Copy in the builtin group sids.
//
SidSize = RtlLengthSid(GlobalBuiltInSid) + sizeof(ULONG);
SidBuffer = (PSID) MIDL_user_allocate(SidSize * BuiltinGroups.Count);
if (SidBuffer == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
InfoToFree->BuiltInSids = SidBuffer;
PBYTE Current = (PBYTE) SidBuffer;
for (Index = 0; Index < BuiltinGroups.Count ; Index++ )
{
RtlCopySid(
RtlLengthSid(GlobalBuiltInSid),
Current,
GlobalBuiltInSid
);
//
// The final value is just a ULONG - appended to the right place.
//
(*RtlSubAuthoritySid(
Current,
((ULONG) (*RtlSubAuthorityCountSid(GlobalBuiltInSid)) - 1)
)) = BuiltinGroups.Element[Index];
SidAndAttributes[Index2].Sid = Current;
SidAndAttributes[Index2].Attributes = SE_GROUP_MANDATORY |
SE_GROUP_ENABLED |
SE_GROUP_ENABLED_BY_DEFAULT;
Index2++;
Current += SidSize;
}
}
InfoToFree->ValidationInfo = LocalValidationInfo;
LocalValidationInfo = NULL;
AuthzInfo->SidCount = GroupCount;
AuthzInfo->SidAndAttributes = SidAndAttributes;
SidAndAttributes = NULL;
Cleanup:
if (LocalValidationInfo)
{
MIDL_user_free(LocalValidationInfo);
}
if (!KERB_SUCCESS(KerbErr))
{
KdcFreeAuthzInfo(InfoToFree);
}
if (IfRelevantData)
{
KerbFreeData(PKERB_IF_RELEVANT_AUTH_DATA_PDU, IfRelevantData);
}
*pStatus = Status;
return KerbErr;
}