712 lines
15 KiB
C
712 lines
15 KiB
C
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1997.
|
|
//
|
|
// File: protocol.c
|
|
//
|
|
// Contents: Implements the XTCB protocol
|
|
//
|
|
// Classes:
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: 3-01-00 RichardW Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "xtcbpkg.h"
|
|
#include "md5.h"
|
|
#include "hmac.h"
|
|
#include <cryptdll.h>
|
|
|
|
//
|
|
// The protocol is very straight-forward. For any group, we have the following:
|
|
//
|
|
// A Group Key (G)
|
|
// A Client Key (C)
|
|
// A Server Key (S)
|
|
//
|
|
//
|
|
// The protocol is as follows:
|
|
//
|
|
// Client (C) sends a message to the server, consisting of:
|
|
// A random seed [ R ]
|
|
// The client's PAC
|
|
// Name of the client and group
|
|
// HMAC( G, S, R, PAC, Name )
|
|
//
|
|
// Both sides create CS and SC keys by:
|
|
// CS = HMAC( [S], G, R, "string1")
|
|
// SC = HMAC( [C], G, R, "string2")
|
|
//
|
|
// The server (S) verifies the HMAC, and replies with:
|
|
// A different seed [ R2 ]
|
|
// HMAC( G, C, R2 )
|
|
//
|
|
|
|
#define CS_HMAC_STRING "Fairly long string for the client-server session key derivation"
|
|
#define SC_HMAC_STRING "Equally long string to derive server-client session key for now"
|
|
|
|
LARGE_INTEGER XtcbExpirationTime = { 0xFFFFFFFF, 0x6FFFFFFF };
|
|
|
|
typedef struct _XTCB_HMAC {
|
|
HMACMD5_CTX Context ;
|
|
} XTCB_HMAC, * PXTCB_HMAC;
|
|
|
|
PXTCB_HMAC
|
|
XtcbInitHmac(
|
|
PUCHAR IndividualKey,
|
|
PUCHAR GroupKey
|
|
)
|
|
{
|
|
PXTCB_HMAC HMac;
|
|
UCHAR Key[ XTCB_SEED_LENGTH * 2 ];
|
|
|
|
HMac = LocalAlloc( LMEM_FIXED, sizeof( XTCB_HMAC ) );
|
|
|
|
if ( HMac )
|
|
{
|
|
RtlCopyMemory( Key,
|
|
IndividualKey,
|
|
XTCB_SEED_LENGTH );
|
|
|
|
RtlCopyMemory( Key+XTCB_SEED_LENGTH,
|
|
GroupKey,
|
|
XTCB_SEED_LENGTH );
|
|
|
|
HMACMD5Init( &HMac->Context,
|
|
Key,
|
|
XTCB_SEED_LENGTH * 2 );
|
|
|
|
|
|
}
|
|
|
|
return HMac ;
|
|
}
|
|
|
|
PXTCB_HMAC
|
|
XtcbPrepareHmac(
|
|
PXTCB_HMAC HMac
|
|
)
|
|
{
|
|
PXTCB_HMAC Working ;
|
|
|
|
Working = LocalAlloc( LMEM_FIXED, sizeof( XTCB_HMAC ) );
|
|
|
|
return Working ;
|
|
}
|
|
|
|
#define XtcbHmacUpdate( H, p, s ) \
|
|
HMACMD5Update( &((PXTCB_HMAC)H)->Context, p, s )
|
|
|
|
|
|
|
|
VOID
|
|
XtcbDeriveKeys(
|
|
PXTCB_CONTEXT Context,
|
|
PUCHAR ServerKey,
|
|
PUCHAR GroupKey,
|
|
PUCHAR ClientKey,
|
|
PUCHAR RandomSeed
|
|
)
|
|
{
|
|
HMACMD5_CTX HMac ;
|
|
|
|
HMACMD5Init( &HMac, ServerKey, XTCB_SEED_LENGTH );
|
|
|
|
HMACMD5Update( &HMac, GroupKey, XTCB_SEED_LENGTH );
|
|
|
|
HMACMD5Update( &HMac, RandomSeed, XTCB_SEED_LENGTH );
|
|
|
|
HMACMD5Update( &HMac, CS_HMAC_STRING, sizeof( CS_HMAC_STRING ) );
|
|
|
|
if ( Context->Core.Type == XtcbContextServer )
|
|
{
|
|
HMACMD5Final( &HMac, Context->Core.InboundKey );
|
|
}
|
|
else
|
|
{
|
|
HMACMD5Final( &HMac, Context->Core.OutboundKey );
|
|
}
|
|
|
|
HMACMD5Init( &HMac, ClientKey, XTCB_SEED_LENGTH );
|
|
|
|
HMACMD5Update( &HMac, GroupKey, XTCB_SEED_LENGTH );
|
|
|
|
HMACMD5Update( &HMac, RandomSeed, XTCB_SEED_LENGTH );
|
|
|
|
HMACMD5Update( &HMac, SC_HMAC_STRING, sizeof( SC_HMAC_STRING ) );
|
|
|
|
if ( Context->Core.Type == XtcbContextServer )
|
|
{
|
|
HMACMD5Final( &HMac, Context->Core.OutboundKey );
|
|
}
|
|
else
|
|
{
|
|
HMACMD5Final( &HMac, Context->Core.InboundKey );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
SECURITY_STATUS
|
|
XtcbBuildInitialToken(
|
|
PXTCB_CREDS Creds,
|
|
PXTCB_CONTEXT Context,
|
|
PSECURITY_STRING Target,
|
|
PSECURITY_STRING Group,
|
|
PUCHAR ServerKey,
|
|
PUCHAR GroupKey,
|
|
PUCHAR ClientKey,
|
|
PUCHAR * Token,
|
|
PULONG TokenLen
|
|
)
|
|
{
|
|
PXTCB_HMAC HMac ;
|
|
PXTCB_INIT_MESSAGE Message ;
|
|
PUCHAR CopyTo ;
|
|
PUCHAR Base ;
|
|
|
|
Message = LsaTable->AllocateLsaHeap( sizeof( XTCB_INIT_MESSAGE ) +
|
|
Creds->Pac->Length +
|
|
XtcbUnicodeDnsName.Length +
|
|
Group->Length );
|
|
|
|
if ( !Message )
|
|
{
|
|
return SEC_E_INSUFFICIENT_MEMORY ;
|
|
}
|
|
|
|
CDGenerateRandomBits( Message->Seed, XTCB_SEED_LENGTH );
|
|
|
|
//
|
|
// Create keys in the context
|
|
//
|
|
|
|
XtcbDeriveKeys(
|
|
Context,
|
|
ServerKey,
|
|
GroupKey,
|
|
ClientKey,
|
|
Message->Seed );
|
|
|
|
//
|
|
// Set random seed in the context
|
|
//
|
|
|
|
RtlCopyMemory(
|
|
Context->Core.RootKey,
|
|
Message->Seed,
|
|
XTCB_SEED_LENGTH );
|
|
|
|
|
|
//
|
|
// Fill it in:
|
|
//
|
|
|
|
Message->Version = 1 ;
|
|
Message->Length = sizeof( XTCB_INIT_MESSAGE ) +
|
|
Creds->Pac->Length +
|
|
XtcbUnicodeDnsName.Length +
|
|
Group->Length ;
|
|
|
|
|
|
|
|
|
|
RtlZeroMemory( Message->HMAC, XTCB_HMAC_LENGTH );
|
|
|
|
CopyTo = (PUCHAR) ( Message + 1 );
|
|
Base = (PUCHAR) Message;
|
|
|
|
RtlCopyMemory(
|
|
CopyTo,
|
|
Creds->Pac,
|
|
Creds->Pac->Length );
|
|
|
|
Message->PacOffset = (ULONG) (CopyTo - Base);
|
|
Message->PacLength = Creds->Pac->Length ;
|
|
CopyTo += Creds->Pac->Length ;
|
|
RtlCopyMemory(
|
|
CopyTo,
|
|
XtcbUnicodeDnsName.Buffer,
|
|
XtcbUnicodeDnsName.Length );
|
|
|
|
Message->OriginatingNode.Buffer = (ULONG) (CopyTo - Base );
|
|
Message->OriginatingNode.Length = XtcbUnicodeDnsName.Length ;
|
|
Message->OriginatingNode.MaximumLength = XtcbUnicodeDnsName.Length ;
|
|
|
|
CopyTo+= XtcbUnicodeDnsName.Length ;
|
|
RtlCopyMemory(
|
|
CopyTo,
|
|
Group->Buffer,
|
|
Group->Length );
|
|
|
|
Message->Group.Buffer = (ULONG) (CopyTo - Base );
|
|
Message->Group.Length = Group->Length ;
|
|
Message->Group.MaximumLength = Group->Length ;
|
|
|
|
|
|
//
|
|
// Structure complete.
|
|
//
|
|
|
|
//
|
|
// Do HMAC
|
|
//
|
|
|
|
|
|
*Token = (PUCHAR) Message ;
|
|
*TokenLen = Message->Length ;
|
|
|
|
return SEC_I_CONTINUE_NEEDED ;
|
|
|
|
}
|
|
|
|
BOOL
|
|
XtcbParseInputToken(
|
|
IN PUCHAR Token,
|
|
IN ULONG TokenLength,
|
|
OUT PSECURITY_STRING Client,
|
|
OUT PSECURITY_STRING Group
|
|
)
|
|
{
|
|
PXTCB_INIT_MESSAGE Message ;
|
|
PWSTR Scan ;
|
|
PUCHAR End ;
|
|
ULONG Chars;
|
|
UNICODE_STRING String = { 0 };
|
|
BOOL Success = FALSE ;
|
|
|
|
*Client = String ;
|
|
*Group = String ;
|
|
|
|
if ( TokenLength < sizeof( XTCB_INIT_MESSAGE ) )
|
|
{
|
|
goto ParseExit;
|
|
|
|
}
|
|
|
|
Message = (PXTCB_INIT_MESSAGE) Token ;
|
|
|
|
if ( Message->Length != TokenLength )
|
|
{
|
|
goto ParseExit;
|
|
}
|
|
|
|
End = Token + Message->Length ;
|
|
|
|
String.Length = Message->OriginatingNode.Length ;
|
|
String.Buffer = (PWSTR) (Token + Message->OriginatingNode.Buffer );
|
|
String.MaximumLength = String.Length ;
|
|
|
|
if ( (PUCHAR) String.Buffer + String.Length > End )
|
|
{
|
|
goto ParseExit;
|
|
}
|
|
|
|
if ( !XtcbDupSecurityString( Client, &String ) )
|
|
{
|
|
goto ParseExit;
|
|
}
|
|
|
|
String.Length = Message->Group.Length ;
|
|
String.Buffer = (PWSTR) (Token + Message->Group.Buffer );
|
|
String.MaximumLength = String.Length ;
|
|
|
|
if ( (PUCHAR) String.Buffer + String.Length > End )
|
|
{
|
|
goto ParseExit;
|
|
}
|
|
|
|
if ( !XtcbDupSecurityString( Group, &String ))
|
|
{
|
|
goto ParseExit ;
|
|
}
|
|
|
|
Success = TRUE ;
|
|
|
|
|
|
ParseExit:
|
|
|
|
if ( !Success )
|
|
{
|
|
if ( Client->Buffer )
|
|
{
|
|
LocalFree( Client->Buffer );
|
|
}
|
|
|
|
if ( Group->Buffer )
|
|
{
|
|
LocalFree( Group->Buffer );
|
|
}
|
|
}
|
|
|
|
return Success ;
|
|
|
|
}
|
|
|
|
|
|
SECURITY_STATUS
|
|
XtcbAuthenticateClient(
|
|
PXTCB_CONTEXT Context,
|
|
PUCHAR Token,
|
|
ULONG TokenLength,
|
|
PUCHAR ClientKey,
|
|
PUCHAR GroupKey,
|
|
PUCHAR MyKey
|
|
)
|
|
{
|
|
PXTCB_INIT_MESSAGE Message ;
|
|
PXTCB_PAC Pac ;
|
|
PLSA_TOKEN_INFORMATION_V2 TokenInfo ;
|
|
ULONG Size ;
|
|
PTOKEN_GROUPS Groups ;
|
|
PUCHAR Scan ;
|
|
PUCHAR Sid1 ;
|
|
NTSTATUS Status = SEC_E_INVALID_TOKEN ;
|
|
PSID Sid ;
|
|
PUCHAR Target ;
|
|
ULONG i ;
|
|
LUID LogonId ;
|
|
UNICODE_STRING UserName ;
|
|
UNICODE_STRING DomainName ;
|
|
HANDLE hToken ;
|
|
NTSTATUS SubStatus ;
|
|
|
|
|
|
|
|
//
|
|
// On entry, we know that the message is in general ok, that the general
|
|
// bounds are ok, but not the PAC. So validate the PAC first before using
|
|
// it.
|
|
//
|
|
|
|
Message = (PXTCB_INIT_MESSAGE) Token ;
|
|
|
|
|
|
XtcbDeriveKeys(
|
|
Context,
|
|
MyKey,
|
|
GroupKey,
|
|
ClientKey,
|
|
Message->Seed );
|
|
|
|
//
|
|
// Got the keys. Let's examine the PAC/
|
|
//
|
|
|
|
Pac = (PXTCB_PAC) ( Token + Message->PacOffset );
|
|
|
|
if ( ( Pac->Length != Message->PacLength ) ||
|
|
( Message->PacLength > TokenLength ) ||
|
|
( Pac->Length > TokenLength ) )
|
|
{
|
|
return SEC_E_INVALID_TOKEN ;
|
|
}
|
|
|
|
//
|
|
// Make sure offsets are within bounds. Each area
|
|
// will still have to confirm that offset + length
|
|
// within limits
|
|
//
|
|
|
|
if ( ( Pac->UserOffset > Pac->Length ) ||
|
|
( Pac->GroupOffset > Pac->Length ) ||
|
|
( Pac->RestrictionOffset > Pac->Length ) ||
|
|
( Pac->NameOffset > Pac->Length ) ||
|
|
( Pac->DomainOffset > Pac->Length ) ||
|
|
( Pac->CredentialOffset > Pac->Length ) )
|
|
{
|
|
return SEC_E_INVALID_TOKEN ;
|
|
}
|
|
|
|
|
|
//
|
|
// 1000 is the current LSA limit. This is not exported to the packages
|
|
// for some reason. This is hard coded right now, but needs to be
|
|
// a global define, or a queryable value out of the LSA.
|
|
//
|
|
|
|
if ( Pac->GroupCount > 1000 )
|
|
{
|
|
return SEC_E_INVALID_TOKEN ;
|
|
}
|
|
|
|
//
|
|
// Looks good, lets start assembling the token information.
|
|
//
|
|
|
|
if ( Pac->GroupLength + Pac->GroupOffset > Pac->Length )
|
|
{
|
|
return SEC_E_INVALID_TOKEN ;
|
|
}
|
|
|
|
Size = sizeof( LSA_TOKEN_INFORMATION_V2 ) + // Basic info
|
|
( Pac->GroupLength ) + // All the group SIDs
|
|
( Pac->UserLength ) + // User SID
|
|
ROUND_UP_COUNT( ( ( Pac->GroupCount * sizeof( SID_AND_ATTRIBUTES ) ) +
|
|
sizeof( TOKEN_GROUPS ) ), ALIGN_LPVOID ) ;
|
|
|
|
TokenInfo = LsaTable->AllocateLsaHeap( Size );
|
|
|
|
if ( TokenInfo == NULL )
|
|
{
|
|
return SEC_E_INSUFFICIENT_MEMORY ;
|
|
}
|
|
|
|
//
|
|
// Now fill in this structure.
|
|
//
|
|
|
|
TokenInfo->ExpirationTime = XtcbExpirationTime ;
|
|
|
|
TokenInfo->DefaultDacl.DefaultDacl = NULL ;
|
|
|
|
TokenInfo->Privileges = NULL ;
|
|
|
|
TokenInfo->Owner.Owner = NULL ;
|
|
|
|
//
|
|
// Set up initial pointers:
|
|
//
|
|
|
|
Groups = (PTOKEN_GROUPS) ( TokenInfo + 1 );
|
|
Target = (PSID) ( (PUCHAR) Groups +
|
|
ROUND_UP_COUNT( ( ( Pac->GroupCount * sizeof( SID_AND_ATTRIBUTES ) ) +
|
|
sizeof( TOKEN_GROUPS ) ), ALIGN_LPVOID ) );
|
|
|
|
//
|
|
// Copy over the user SID
|
|
//
|
|
|
|
if ( Pac->UserOffset + Pac->UserLength > Pac->Length )
|
|
{
|
|
Status = SEC_E_INVALID_TOKEN ;
|
|
|
|
goto Cleanup ;
|
|
}
|
|
|
|
Sid = (PSID) ((PUCHAR) Pac + Pac->UserOffset) ;
|
|
|
|
if ( !RtlValidSid( Sid ) )
|
|
{
|
|
Status = SEC_E_INVALID_TOKEN ;
|
|
|
|
goto Cleanup ;
|
|
|
|
}
|
|
|
|
if ( RtlLengthSid( Sid ) != Pac->UserLength )
|
|
{
|
|
Status = SEC_E_INVALID_TOKEN ;
|
|
|
|
goto Cleanup ;
|
|
}
|
|
|
|
RtlCopySid( Pac->UserLength,
|
|
(PSID) Target,
|
|
Sid );
|
|
|
|
Target += RtlLengthSid( Sid );
|
|
|
|
TokenInfo->User.User.Sid = (PSID) Target ;
|
|
TokenInfo->User.User.Attributes = 0 ;
|
|
|
|
|
|
//
|
|
// Now, do the groups. Since all the SIDs are in one
|
|
// contiguous block, the plan is to copy them over
|
|
// whole, then iterate through the list and fix up the
|
|
// pointers in the group list.
|
|
//
|
|
|
|
RtlCopyMemory(
|
|
Target,
|
|
(PUCHAR) Pac + Pac->GroupOffset,
|
|
Pac->GroupLength );
|
|
|
|
|
|
Scan = Target ;
|
|
Target += Pac->GroupLength ;
|
|
i = 0 ;
|
|
|
|
while ( Scan < Target )
|
|
{
|
|
Sid = (PSID) Scan ;
|
|
|
|
if ( RtlValidSid( Sid ) )
|
|
{
|
|
//
|
|
// This is an ok SID.
|
|
//
|
|
|
|
Groups->Groups[ i ].Sid = Sid ;
|
|
Groups->Groups[ i ].Attributes = SE_GROUP_MANDATORY |
|
|
SE_GROUP_ENABLED |
|
|
SE_GROUP_ENABLED_BY_DEFAULT ;
|
|
|
|
if ( i == 0 )
|
|
{
|
|
TokenInfo->PrimaryGroup.PrimaryGroup = Sid ;
|
|
}
|
|
|
|
i++ ;
|
|
|
|
Scan += RtlLengthSid( Sid );
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// On exit, if Scan is less than Target, then we failed to
|
|
// process all the SIDs. Bail out
|
|
//
|
|
|
|
if ( Scan < Target )
|
|
{
|
|
Status = SEC_E_INVALID_TOKEN ;
|
|
goto Cleanup ;
|
|
}
|
|
|
|
//
|
|
// Pull out the user name/etc
|
|
//
|
|
|
|
if ( Pac->NameLength + Pac->NameOffset > Pac->Length )
|
|
{
|
|
Status = SEC_E_INVALID_TOKEN ;
|
|
goto Cleanup ;
|
|
}
|
|
|
|
UserName.Buffer = (PWSTR) ((PUCHAR) Pac + Pac->NameOffset);
|
|
UserName.Length = (WORD) Pac->NameLength ;
|
|
UserName.MaximumLength = UserName.Length ;
|
|
|
|
|
|
if ( Pac->DomainLength + Pac->DomainOffset > Pac->Length )
|
|
{
|
|
Status = SEC_E_INVALID_TOKEN ;
|
|
goto Cleanup ;
|
|
}
|
|
|
|
DomainName.Buffer = (PWSTR) ((PUCHAR) Pac + Pac->DomainOffset );
|
|
DomainName.Length = (WORD) Pac->DomainLength ;
|
|
DomainName.MaximumLength = DomainName.Length ;
|
|
|
|
//
|
|
// We've assembled the token info. Now, create the logon session
|
|
//
|
|
|
|
DebugLog(( DEB_TRACE, "Creating logon for %wZ\\%wZ\n", &DomainName,
|
|
&UserName ));
|
|
|
|
|
|
AllocateLocallyUniqueId( &LogonId );
|
|
|
|
Status = LsaTable->CreateLogonSession( &LogonId );
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
goto Cleanup ;
|
|
}
|
|
|
|
//
|
|
// Create the token to represent this user:
|
|
//
|
|
|
|
Status = LsaTable->CreateToken(
|
|
&LogonId,
|
|
&XtcbSource,
|
|
Network,
|
|
TokenImpersonation,
|
|
LsaTokenInformationV2,
|
|
TokenInfo,
|
|
NULL,
|
|
&UserName,
|
|
&DomainName,
|
|
&XtcbComputerName,
|
|
NULL,
|
|
&hToken,
|
|
&SubStatus );
|
|
|
|
TokenInfo = NULL ;
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
Status = SubStatus ;
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
Context->Token = hToken ;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if ( TokenInfo )
|
|
{
|
|
LsaTable->FreeLsaHeap( TokenInfo );
|
|
}
|
|
|
|
return Status ;
|
|
}
|
|
|
|
|
|
SECURITY_STATUS
|
|
XtcbBuildReplyToken(
|
|
PXTCB_CONTEXT Context,
|
|
ULONG fContextReq,
|
|
PSecBuffer pOutput
|
|
)
|
|
{
|
|
PXTCB_INIT_MESSAGE_REPLY Reply ;
|
|
NTSTATUS Status ;
|
|
|
|
if ( fContextReq & ASC_REQ_ALLOCATE_MEMORY )
|
|
{
|
|
Reply = LsaTable->AllocateLsaHeap( sizeof( XTCB_INIT_MESSAGE_REPLY ) );
|
|
}
|
|
else
|
|
{
|
|
if ( pOutput->cbBuffer >= sizeof( XTCB_INIT_MESSAGE_REPLY ) )
|
|
{
|
|
Reply = pOutput->pvBuffer ;
|
|
}
|
|
else
|
|
{
|
|
Reply = NULL ;
|
|
}
|
|
}
|
|
|
|
if ( Reply == NULL )
|
|
{
|
|
Status = SEC_E_INSUFFICIENT_MEMORY ;
|
|
|
|
goto Cleanup ;
|
|
}
|
|
|
|
Reply->Version = 1;
|
|
Reply->Length = sizeof( XTCB_INIT_MESSAGE_REPLY );
|
|
|
|
CDGenerateRandomBits(
|
|
Reply->ReplySeed,
|
|
XTCB_SEED_LENGTH );
|
|
|
|
pOutput->cbBuffer = sizeof( XTCB_INIT_MESSAGE_REPLY );
|
|
pOutput->pvBuffer = Reply ;
|
|
Reply = NULL ;
|
|
|
|
|
|
Cleanup:
|
|
if ( Reply )
|
|
{
|
|
LsaTable->FreeLsaHeap( Reply );
|
|
}
|
|
return Status ;
|
|
}
|