//+----------------------------------------------------------------------- // // File: pac.cxx // // Contents: KDC Pac generation code. // // // History: 16-Jan-93 WadeR Created. // //------------------------------------------------------------------------ #include "kdcsvr.hxx" #include #include "kdctrace.h" #include "fileno.h" #include #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); }