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