windows-nt/Source/XPSP1/NT/ds/security/protocols/kerberos/server/pac.cxx
2020-09-26 16:20:57 +08:00

2003 lines
48 KiB
C++

//+-----------------------------------------------------------------------
//
// 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>
#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
)
{
NTSTATUS hrRet = S_OK;
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 );
}
}
}
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:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcBuildPacSidList(
IN PNETLOGON_VALIDATION_SAM_INFO3 UserInfo,
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);
}
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++;
}
}
//
// Deallocate any memory we've allocated
//
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 grousp and builds a new PAC with
// those groups
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcAddResourceGroupsToPac(
IN PPACTYPE OldPac,
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"));
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,
&SidList
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Call SAM to get the sids
//
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;
}
//
// 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 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, NewPac = NULL;
ULONG LocalPacSize;
KDC_TICKET_INFO KdcTicketInfo = {0};
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(
KDC_PAC_CHECKSUM,
&Check
);
if (!NT_SUCCESS(Status))
{
KerbErr = KDC_ERR_ETYPE_NOTSUPP;
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;
}
//
// If we are to add local groups, do so now
//
if (AddResourceGroups)
{
KerbErr = KdcAddResourceGroupsToPac(
Pac,
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 = 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 = 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;
}
//
// 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,
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 ( ( 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;
PPAC_INFO_BUFFER LogonInfo;
UCHAR LocalChecksum[20];
UCHAR LocalServerChecksum[20];
UCHAR LocalPrivSvrChecksum[20];
PPACTYPE Pac;
KDC_TICKET_INFO KdcTicketInfo = {0};
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;
}
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;
}
//
// 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_ETYPE_NOTSUPP;
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,
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_ETYPE_NOTSUPP;
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,
ServerSignature->Signature
);
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 (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
// 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 PKERB_INTERNAL_NAME S4UClientName,
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(
KDC_PAC_CHECKSUM,
&Check
);
if (!NT_SUCCESS(Status))
{
KerbErr = KDC_ERR_ETYPE_NOTSUPP;
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
KerbConvertGeneralizedTimeToLargeInt(
&ClientId,
&EncryptedTicket->authtime,
0 // no usec
);
//
// Put the S4U client in the pac verifier.
//
if (ARGUMENT_PRESENT(S4UClientName))
{
KerbErr = KerbConvertKdcNameToString(
&ClientName,
S4UClientName,
NULL
);
}
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,
&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
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,
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: 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;
ULONG LocalPacSize;
PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo = NULL;
SAMPR_PSID_ARRAY ZeroResourceGroups;
PUNICODE_STRING TrustedForest = NULL;
if (NULL != ServerInfo->TrustSid)
{
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;
}
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"));
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;
}
//
// Assumption is that if the Trust SID is in the ServerInfo then this is an
// outbound trust with the TRUST_ATTRIBUTE_FILTER_SIDS bit set.
//
if ((ServerInfo->TrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0)
{
TrustedForest = &(ServerInfo->TrustedForest);
DebugLog((DEB_TRACE, "Filtering Sids for forest %wZ\n", TrustedForest));
}
Status = LsaIFilterSids(
TrustedForest, // Pass domain name here
TRUST_DIRECTION_OUTBOUND,
TRUST_TYPE_UPLEVEL,
ServerInfo->TrustAttributes,
ServerInfo->TrustSid,
NetlogonValidationSamInfo2,
ValidationInfo
);
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;
KdcLsaIAuditKdcEvent(
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
);
}
DebugLog((DEB_ERROR,"Failed to filter SIDS (LsaIFilterSids): 0x%x\n",Status));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Now build a new pac
//
Status = PAC_InitAndUpdateGroups(
ValidationInfo,
&ZeroResourceGroups,
OldPac,
&NewPac
);
if (!NT_SUCCESS(Status))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
LocalPacSize = PAC_GetSize(NewPac);
if (!PAC_ReMarshal(NewPac, LocalPacSize))
{
DsysAssert(!"PAC_Remarshal Failed");
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
if (*PacData != (PBYTE)NewPac)
{
MIDL_user_free(*PacData);
*PacData = (PBYTE) NewPac;
NewPac = NULL;
*PacSize = LocalPacSize;
}
}
Cleanup:
if (NewPac != NULL)
{
MIDL_user_free(NewPac);
}
if (ValidationInfo != NULL)
{
MIDL_user_free(ValidationInfo);
}
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 BOOLEAN AddResourceGroups,
IN OUT PKERB_AUTHORIZATION_DATA PacAuthData
)
{
PPAC_SIGNATURE_DATA ServerSignature;
ULONG ServerSiganatureSize;
PPAC_SIGNATURE_DATA PrivSvrSignature;
ULONG PrivSvrSiganatureSize;
KERBERR KerbErr = KDC_ERR_NONE;
TRACE(KDC, KdcVerifyAndResignPac, DEB_FUNCTION);
//
// Now verify the existing signature
//
KerbErr = KdcVerifyPacSignature(
OldKey,
OldServerInfo,
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;
}
//
// 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,
&PacAuthData->value.auth_data.value,
(PULONG) &PacAuthData->value.auth_data.length
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
Cleanup:
return(KerbErr);
}