|
|
//+-----------------------------------------------------------------------
//
// 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);
}
|