windows-nt/Source/XPSP1/NT/ds/security/protocols/xtcb/protocol.c
2020-09-26 16:20:57 +08:00

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 ;
}