/*++ Copyright (c) 1998-2001 Microsoft Corporation Module Name: security.c Abstract: Domain Name System (DNS) Library DNS secure update API. Author: Jim Gilroy (jamesg) January, 1998 Revision History: --*/ #include "local.h" #include "time.h" // time() function // security headers #define SECURITY_WIN32 #include "sspi.h" #include "issperr.h" #include "rpc.h" #include "rpcndr.h" #include "ntdsapi.h" // security definitions #define SIG_LEN 33 #define NAME_OWNER "." // root node #define SEC_SUCCESS(Status) ((Status) >= 0) #define PACKAGE_NAME L"negotiate" #define NT_DLL_NAME "security.dll" // // Maximum length of data signed // - full packet, length, and sig // // If a problem can use packet buffer length and sig length and allocate that // #define MAX_SIGNING_SIZE (0x11000) // // Global Sspi credentials handle // SECURITY_INTEGER g_SspiCredentialsLifetime = { 0, 0 }; CredHandle g_hSspiCredentials; TimeStamp g_SspiCredentialsLifetime; #define SSPI_INVALID_HANDLE(x) \ ( ((PSecHandle) (x))->dwLower == (ULONG_PTR) -1 && \ ((PSecHandle) (x))->dwUpper == (ULONG_PTR) -1 ) // // DEV_NOTE: Security ticket expiration // // Security team is yet unsure about how to use the expiration time & // currently tix are valid forever. If it becomes invalid accept/init context // will re-nego a new one for us underneath so we should concern ourselves // at this point. Still, in principal they say we may need to worry about it // in the future... // #define SSPI_EXPIRED_HANDLE( x ) ( FALSE ) // // Currently only negotiate kerberos // // DCR: tie this to regkey, then set in init function // BOOL g_NegoKerberosOnly = TRUE; // // Context "key" for TKEYs // typedef struct _DNS_SECCTXT_KEY { IP4_ADDRESS IpRemote; PSTR pszTkeyName; PSTR pszClientContext; PWSTR pwsCredKey; } DNS_SECCTXT_KEY, *PDNS_SECCTXT_KEY; // // Context name uniqueness // // Tick helps insure uniqueness of context name LONG g_ContextCount = 0; // UUID insures uniqueness across IP reuse CHAR g_ContextUuid[ GUID_STRING_BUFFER_LENGTH ] = {0}; // // Security context request blob // typedef struct _DNS_SECCTXT_REQUEST { LPSTR pszServerName; PCHAR pCredentials; LPSTR pszContext; DWORD dwFlag; IP_ADDRESS ipServer; PIP_ARRAY aipServer; } DNS_SECCTXT_REQUEST, *PDNS_SECCTXT_REQUEST; // // Security context // typedef struct _DnsSecurityContext { struct _DnsSecurityContext * pNext; struct _SecHandle hSecHandle; DNS_SECCTXT_KEY Key; CredHandle CredHandle; // context info DWORD Version; WORD TkeySize; // context state BOOL fNewConversation; BOOL fNegoComplete; BOOL fEchoToken; BOOL fHaveSecHandle; BOOL fHaveCredHandle; BOOL fClient; // timeout DWORD dwCreateTime; DWORD dwCleanupTime; DWORD dwExpireTime; } SEC_CNTXT, *PSEC_CNTXT; // // Security session info. // Held only during interaction, not cached // typedef struct _SecPacketInfo { PSEC_CNTXT pSecContext; SecBuffer RemoteBuf; SecBuffer LocalBuf; PDNS_HEADER pMsgHead; PCHAR pMsgEnd; PDNS_RECORD pTsigRR; PDNS_RECORD pTkeyRR; PCHAR pszContextName; DNS_PARSED_RR ParsedRR; // client must save signature of query to verify sig on response PCHAR pQuerySig; WORD QuerySigLength; WORD ExtendedRcode; // version on TKEY \ TSIG DWORD TkeyVersion; } SECPACK, *PSECPACK; // // DNS API context // typedef struct _DnsAPIContext { DWORD Flags; PVOID Credentials; PSEC_CNTXT pSecurityContext; } DNS_API_CONTEXT, *PDNS_API_CONTEXT; // // TCP timeout // #define DEFAULT_TCP_TIMEOUT 10 #define SECURE_UPDATE_TCP_TIMEOUT (15) // // Public security globals (exposed in dnslib.h) // BOOL g_fSecurityPackageInitialized = FALSE; // // Private security globals // HINSTANCE g_hLibSecurity; PSecurityFunctionTableW g_pSecurityFunctionTable; DWORD g_SecurityTokenMaxLength = 0; DWORD g_SignatureMaxLength = 0; // // Security context caching // PSEC_CNTXT SecurityContextListHead = NULL; CRITICAL_SECTION SecurityContextListCS; DWORD SecContextCreate = 0; DWORD SecContextFree = 0; DWORD SecContextQueue = 0; DWORD SecContextQueueInNego = 0; DWORD SecContextDequeue = 0; DWORD SecContextTimeout = 0; // // Security packet info memory tracking // DWORD SecPackAlloc = 0; DWORD SecPackFree = 0; // // Security packet verifications // DWORD SecTkeyInvalid = 0; DWORD SecTkeyBadTime = 0; DWORD SecTsigFormerr = 0; DWORD SecTsigEcho = 0; DWORD SecTsigBadKey = 0; DWORD SecTsigVerifySuccess = 0; DWORD SecTsigVerifyFailed = 0; // // Hacks // // Allowing old TSIG off by default, server can turn on. BOOL SecAllowOldTsig = 0; // 1 to allow old sigs, 2 any sig DWORD SecTsigVerifyOldSig = 0; DWORD SecTsigVerifyOldFailed = 0; // // TIME values // // (in seconds) #define TIME_WEEK_S 604800 #define TIME_DAY_S 86400 #define TIME_10_HOUR_S 36000 #define TIME_8_HOUR_S 28800 #define TIME_4_HOUR_S 14400 #define TIME_HOUR_S 3600 #define TIME_10_MINUTE_S 600 #define TIME_5_MINUTE_S 300 #define TIME_3_MINUTE_S 160 #define TIME_MINUTE_S 60 // Big Time skew on by default DWORD SecBigTimeSkew = TIME_DAY_S; DWORD SecBigTimeSkewBypass = 0; // // TSIG - GSS alogrithm // #define W2K_GSS_ALGORITHM_NAME_PACKET ("\03gss\011microsoft\03com") #define W2K_GSS_ALGORITHM_NAME_PACKET_LENGTH (sizeof(W2K_GSS_ALGORITHM_NAME_PACKET)) #define GSS_ALGORITHM_NAME_PACKET ("\010gss-tsig") #define GSS_ALGORITHM_NAME_PACKET_LENGTH (sizeof(GSS_ALGORITHM_NAME_PACKET)) PCHAR g_pAlgorithmNameW2K = W2K_GSS_ALGORITHM_NAME_PACKET; PCHAR g_pAlgorithmNameCurrent = GSS_ALGORITHM_NAME_PACKET; // // TKEY context name // #define MAX_CONTEXT_NAME_LENGTH DNS_MAX_NAME_BUFFER_LENGTH // // TKEY/TSIG versioning // // Win2K shipped with some deviations from current the GSS-TSIG RFC. // Specifically // - client sent TKEY query in Answer section instead of addtional // - alg name was "gss.microsoft.com", new name is "gss-tsig" // - client would reuse context based on process id, rather than // forcing unique context // - signing didn't include length when including previous sig // // Defining versioning -- strictly internal to this module // #define TKEY_VERSION_W2K 3 #define TKEY_VERSION_WHISTLER_BETA 4 #define TKEY_VERSION_XP_BAD_SIG 5 #define TKEY_VERSION_XP_RC1 6 #define TKEY_VERSION_XP 7 #define TKEY_VERSION_CURRENT TKEY_VERSION_XP // // TKEY expiration // - cleanup if inactive for 3 minutes // - max kept alive four hours then must renego // #define TKEY_CLEANUP_INTERVAL (TIME_3_MINUTE_S) // // DCR_FIX: Nego time issue (GM vs local time) // // Currently netlogon seems to run in GM time, so we limit our time // check to one day. Later on, we should move it back to 1 hour. // #define TKEY_EXPIRE_INTERVAL (TIME_DAY_S) #define TSIG_EXPIRE_INTERVAL (TIME_10_HOUR_S) #define TKEY_MAX_EXPIRE_INTERVAL (TIME_4_HOUR_S) #define MAX_TIME_SKEW (TIME_DAY_S) // // ntdsapi.dll loading // - for making SPN for DNS server // #define NTDSAPI_DLL_NAMEW L"ntdsapi.dll" #define MAKE_SPN_FUNC "DsClientMakeSpnForTargetServerW" FARPROC g_pfnMakeSpn = NULL; HMODULE g_hLibNtdsa = NULL; // // Private protos // VOID DnsPrint_SecurityContextList( IN PRINT_ROUTINE PrintRoutine, IN OUT PPRINT_CONTEXT pPrintContext, IN LPSTR pszHeader, IN PSEC_CNTXT pListHead ); VOID DnsPrint_SecurityContext( IN PRINT_ROUTINE PrintRoutine, IN OUT PPRINT_CONTEXT pPrintContext, IN LPSTR pszHeader, IN PSEC_CNTXT pSecCtxt ); VOID DnsPrint_SecurityPacketInfo( IN PRINT_ROUTINE PrintRoutine, IN OUT PPRINT_CONTEXT pPrintContext, IN LPSTR pszHeader, IN PSECPACK pSecPack ); #if DBG #define DnsDbg_SecurityContextList(a,b) DnsPrint_SecurityContextList(DnsPR,NULL,a,b) #define DnsDbg_SecurityContext(a,b) DnsPrint_SecurityContext(DnsPR,NULL,a,b) #define DnsDbg_SecurityPacketInfo(a,b) DnsPrint_SecurityPacketInfo(DnsPR,NULL,a,b) #else #define DnsDbg_SecurityContextList(a,b) #define DnsDbg_SecurityContext(a,b) #define DnsDbg_SecurityPacketInfo(a,b) #endif #define Dns_FreeSecurityPacketInfo(p) Dns_CleanupSecurityPacketInfoEx((p),TRUE) #define Dns_ResetSecurityPacketInfo(p) Dns_CleanupSecurityPacketInfoEx((p),FALSE) DNS_STATUS Dns_LoadNtdsapiProcs( VOID ); PWSTR MakeCredKey( IN PCHAR pCreds ); BOOL CompareCredKeys( IN PWSTR pwsCredKey1, IN PWSTR pwsCredKey2 ); DNS_STATUS Dns_AcquireCredHandle( OUT PCredHandle pCredHandle, IN BOOL fDnsServer, IN PCHAR pCreds ); // // Security session packet info // PSECPACK Dns_CreateSecurityPacketInfo( VOID ) /*++ Routine Description: Create security packet info structure. Arguments: None. Return Value: Ptr to new zeroed security packet info. --*/ { PSECPACK psecPack; psecPack = (PSECPACK) ALLOCATE_HEAP_ZERO( sizeof(SECPACK) ); if ( !psecPack ) { return( NULL ); } SecPackAlloc++; return( psecPack ); } VOID Dns_InitSecurityPacketInfo( OUT PSECPACK pSecPack, IN PSEC_CNTXT pSecCtxt ) /*++ Routine Description: Init security packet info for given context Arguments: Return Value: None. --*/ { // clear previous info RtlZeroMemory( pSecPack, sizeof(SECPACK) ); // set context ptr pSecPack->pSecContext = pSecCtxt; } VOID Dns_CleanupSecurityPacketInfoEx( IN OUT PSECPACK pSecPack, IN BOOL fFree ) /*++ Routine Description: Cleans up security packet info. Arguments: pSecPack -- ptr to security packet info to clean up Return Value: None. --*/ { if ( !pSecPack ) { return; } if ( pSecPack->pszContextName ) { FREE_HEAP( pSecPack->pszContextName ); } if ( pSecPack->pTsigRR ) { FREE_HEAP( pSecPack->pTsigRR ); //Dns_RecordFree( pSecPack->pTsigRR ); } if ( pSecPack->pTkeyRR ) { FREE_HEAP( pSecPack->pTkeyRR ); //Dns_RecordFree( pSecPack->pTkeyRR ); } if ( pSecPack->pQuerySig ) { FREE_HEAP( pSecPack->pQuerySig ); } if ( pSecPack->LocalBuf.pvBuffer ) { FREE_HEAP( pSecPack->LocalBuf.pvBuffer ); } if ( fFree ) { FREE_HEAP( pSecPack ); SecPackFree++; } else { RtlZeroMemory( pSecPack, sizeof(SECPACK) ); } } VOID DnsPrint_SecurityPacketInfo( IN PRINT_ROUTINE PrintRoutine, IN OUT PPRINT_CONTEXT pPrintContext, IN LPSTR pszHeader, IN PSECPACK pSecPack ) { if ( !pSecPack ) { PrintRoutine( pPrintContext, "%s NULL security context\n", pszHeader ? pszHeader : "" ); return; } DnsPrint_Lock(); PrintRoutine( pPrintContext, "%s\n" "\tptr = %p\n" "\tpSec Context = %p\n" "\tContext Name = %s\n" "\tVersion = %d\n" "\tpTsigRR = %p\n" "\tpTkeyRR = %p\n" "\tExt RCODE = %d\n" "\tremote buf = %p\n" "\t length = %d\n" "\tlocal buf = %p\n" "\t length = %d\n", pszHeader ? pszHeader : "Security packet info:", pSecPack, pSecPack->pSecContext, pSecPack->pszContextName, pSecPack->TkeyVersion, pSecPack->pTsigRR, pSecPack->pTkeyRR, pSecPack->ExtendedRcode, pSecPack->RemoteBuf.pvBuffer, pSecPack->RemoteBuf.cbBuffer, pSecPack->LocalBuf.pvBuffer, pSecPack->LocalBuf.cbBuffer ); DnsPrint_ParsedRecord( PrintRoutine, pPrintContext, "Parsed Security RR", & pSecPack->ParsedRR ); if ( pSecPack->pTsigRR ) { DnsPrint_Record( PrintRoutine, pPrintContext, "TSIG RR", pSecPack->pTsigRR, NULL // no previous record ); } if ( pSecPack->pTkeyRR ) { DnsPrint_Record( PrintRoutine, pPrintContext, "TKEY RR", pSecPack->pTkeyRR, NULL // no previous record ); } if ( pSecPack->pSecContext ) { DnsPrint_SecurityContext( PrintRoutine, pPrintContext, "Associated Security Context", pSecPack->pSecContext ); } DnsPrint_Unlock(); } // // Security context routines // PSEC_CNTXT Dns_CreateSecurityContext( VOID ) /*++ Routine Description: Allocate a new security context blob. Arguments: None. Return Value: Ptr to new context. NULL on alloc failure. --*/ { PSEC_CNTXT psecCtxt; psecCtxt = (PSEC_CNTXT) ALLOCATE_HEAP_ZERO( sizeof(SEC_CNTXT) ); if ( !psecCtxt ) { return( NULL ); } psecCtxt->fNewConversation = TRUE; SecContextCreate++; return( psecCtxt ); } VOID Dns_FreeSecurityContext( IN OUT PSEC_CNTXT pSecCtxt ) /*++ Routine Description: Cleans up security session data. Arguments: pSecCtxt -- handle to context to clean up Return Value: TRUE if successful FALSE otherwise --*/ { PSEC_CNTXT psecCtxt = (PSEC_CNTXT)pSecCtxt; if ( !psecCtxt ) { return; } if ( psecCtxt->Key.pszTkeyName ) { FREE_HEAP( psecCtxt->Key.pszTkeyName ); } if ( psecCtxt->Key.pszClientContext ) { FREE_HEAP( psecCtxt->Key.pszClientContext ); } if ( psecCtxt->Key.pwsCredKey ) { FREE_HEAP( psecCtxt->Key.pwsCredKey ); } if ( psecCtxt->fHaveSecHandle ) { g_pSecurityFunctionTable->DeleteSecurityContext( &psecCtxt->hSecHandle ); } if ( psecCtxt->fHaveCredHandle ) { g_pSecurityFunctionTable->FreeCredentialsHandle( &psecCtxt->CredHandle ); } FREE_HEAP( psecCtxt ); SecContextFree++; } // // Security context list routines // // Server side may have multiple security sessions active and does // not maintain client state on a thread's stack, so must have // a list to hold previous session info. // PSEC_CNTXT Dns_DequeueSecurityContextByKey( IN DNS_SECCTXT_KEY Key, IN BOOL fComplete ) /*++ Routine Description: Get security session context from session list based on key. Arguments: Key -- session key fComplete -- TRUE if need fully negotiated context FALSE if still in negotiation Return Value: Handle to security session context, if found. NULL if no context for key. --*/ { PSEC_CNTXT pcur; PSEC_CNTXT pback; DWORD currentTime = Dns_GetCurrentTimeInSeconds(); DNSDBG( SECURITY, ( "DequeueSecurityContext()\n" "\tIP = %s\n" "\tTKEY name = %s\n" "\tcontext name = %s\n" "\tcred string = %S\n", IP_STRING( Key.IpRemote ), Key.pszTkeyName, Key.pszClientContext, Key.pwsCredKey )); EnterCriticalSection( &SecurityContextListCS ); IF_DNSDBG( SECURITY ) { DnsDbg_SecurityContextList( "Before Get", SecurityContextListHead ); } pback = (PSEC_CNTXT) &SecurityContextListHead; while ( pcur = pback->pNext ) { // if context is stale -- delete it if ( pcur->dwCleanupTime < currentTime ) { pback->pNext = pcur->pNext; SecContextTimeout++; Dns_FreeSecurityContext( pcur ); continue; } // match context to key // - must match IP // - server side must match TKEY name // - client side must match context key if ( Key.IpRemote == pcur->Key.IpRemote && ( ( Key.pszTkeyName && Dns_NameCompare_UTF8( Key.pszTkeyName, pcur->Key.pszTkeyName )) || ( Key.pszClientContext && Dns_NameCompare_UTF8( Key.pszClientContext, pcur->Key.pszClientContext )) ) && CompareCredKeys( Key.pwsCredKey, pcur->Key.pwsCredKey ) ) { // if expect completed context, ignore incomplete // // DCR: should dump once RFC compliant if ( fComplete && !pcur->fNegoComplete ) { DNSDBG( ANY, ( "WARNING: Requested dequeue security context still in nego!\n" "\tmatching key %s %s\n" "\tcontext complete = %d\n" "\trequest fComplete = %d\n", Key.pszTkeyName, IP_STRING( Key.IpRemote ), pcur->fNegoComplete, fComplete )); pback = pcur; continue; } // detach context // DCR: could ref count context and leave in // not sure this adds much -- how many process do MT // updates in same security context pback->pNext = pcur->pNext; SecContextDequeue++; break; } // not found -- continue search pback = pcur; } IF_DNSDBG( SECURITY ) { DnsDbg_SecurityContextList( "After Dequeue", SecurityContextListHead ); } LeaveCriticalSection( &SecurityContextListCS); return( pcur ); } PSEC_CNTXT Dns_FindOrCreateSecurityContext( IN DNS_SECCTXT_KEY Key ) /*++ Routine Description: Find and extract existing security context from list, OR create a new one. Arguments: Key -- key for context Return Value: Ptr to security context. --*/ { PSEC_CNTXT psecCtxt; DNSDBG( SECURITY, ( "Dns_FindOrCreateSecurityContext()\n" )); // find existing context psecCtxt = Dns_DequeueSecurityContextByKey( Key, FALSE ); if ( psecCtxt ) { return psecCtxt; } // // create context // // server's will come with complete TKEY name from packet // client's will come with specific context name, we must // generate globally unique name // - context count // - tick count // - UUID // // implementation notes: // - UUID to make sure we're unique across IP reuse // // - UUID and timer enforce uniqueness across process shutdown // and restart (even if generation UUID fails, you'll be at // a different tick count) // // - context count enforces uniqueness within process // - interlock allows us to eliminate thread id // - even with thread id, we'd still need this anyway // (without interlock) to back up timer since GetTickCount() // is "chunky" and a thread could concievably not "tick" // between contexts on the same thread if they were dropped // before going to the wire // // psecCtxt = Dns_CreateSecurityContext(); if ( psecCtxt ) { PSTR pstr; PSTR pnameTkey; CHAR nameBuf[ DNS_MAX_NAME_BUFFER_LENGTH ]; pnameTkey = Key.pszTkeyName; if ( Key.pszClientContext ) { LONG count = InterlockedIncrement( &g_ContextCount ); // // Note: it is important that this string is in canonical // form as per RFC 2535 section 8.1 - basically this means // lower case. // _snprintf( nameBuf, MAX_CONTEXT_NAME_LENGTH, "%s.%d-%x.%s", Key.pszClientContext, count, GetTickCount(), g_ContextUuid ); nameBuf[ DNS_MAX_NAME_BUFFER_LENGTH ] = 0; pnameTkey = nameBuf; pstr = Dns_CreateStringCopy_A( Key.pszClientContext ); if ( !pstr ) { goto Failed; } psecCtxt->Key.pszClientContext = pstr; } // remote IP psecCtxt->Key.IpRemote = Key.IpRemote; // TKEY name pstr = Dns_CreateStringCopy_A( pnameTkey ); if ( !pstr ) { goto Failed; } psecCtxt->Key.pszTkeyName = pstr; // cred key if ( Key.pwsCredKey ) { pstr = (PSTR) Dns_CreateStringCopy_W( Key.pwsCredKey ); if ( !pstr ) { goto Failed; } psecCtxt->Key.pwsCredKey = (PWSTR) pstr; } } IF_DNSDBG( SECURITY ) { DnsDbg_SecurityContextList( "New security context:", psecCtxt ); } return( psecCtxt ); Failed: // memory allocation failure Dns_FreeSecurityContext( psecCtxt ); return NULL; } VOID Dns_EnlistSecurityContext( IN OUT PSEC_CNTXT pSecCtxt ) /*++ Routine Description: Enlist a security context. Note this does NOT create the context it simply enlists a current one. Arguments: Key -- key for context Return Value: Handle to security context. --*/ { PSEC_CNTXT pnew = (PSEC_CNTXT)pSecCtxt; DWORD currentTime; // // catch queuing up some bogus blob // ASSERT( pnew->fNewConversation == TRUE || pnew->fNewConversation == FALSE ); ASSERT( pnew->dwCreateTime < pnew->dwCleanupTime || pnew->dwCleanupTime == 0 ); ASSERT( pnew->Key.pszTkeyName ); ASSERT( pnew->Key.IpRemote ); // // reset expire time so keep context active if in use // // DCR_FIX: need expire time to use min of TKEY and fixed hard timeout // currentTime = Dns_GetCurrentTimeInSeconds(); if ( !pnew->dwCreateTime ) { pnew->dwCreateTime = currentTime; } if ( !pnew->dwExpireTime ) { pnew->dwExpireTime = currentTime + TKEY_MAX_EXPIRE_INTERVAL; } // // cleanup after interval not used // unconditionally maximum of cleanup interval. // pnew->dwCleanupTime = currentTime + TKEY_CLEANUP_INTERVAL; EnterCriticalSection( &SecurityContextListCS ); pnew->pNext = SecurityContextListHead; SecurityContextListHead = pnew; SecContextQueue++; if ( !pnew->fNegoComplete ) { SecContextQueueInNego++; } IF_DNSDBG( SECURITY ) { DnsDbg_SecurityContextList( "After add", SecurityContextListHead ); } LeaveCriticalSection( &SecurityContextListCS ); } VOID Dns_TimeoutSecurityContextList( IN BOOL fClearList ) /*++ Routine Description: Eliminate old session data. Arguments: fClearList -- TRUE to delete all, FALSE to timeout Return Value: None. --*/ { PSEC_CNTXT pcur; PSEC_CNTXT pback; DWORD currentTime; if ( fClearList ) { currentTime = MAXDWORD; } else { currentTime = Dns_GetCurrentTimeInSeconds(); } EnterCriticalSection( &SecurityContextListCS ); pback = (PSEC_CNTXT) &SecurityContextListHead; while ( pcur = pback->pNext ) { // if haven't reached cleanup time, keep in list if ( pcur->dwCleanupTime > currentTime ) { pback = pcur; continue; } // entry has expired // - cut from list // - free the session context pback->pNext = pcur->pNext; SecContextTimeout++; Dns_FreeSecurityContext( pcur ); } ASSERT( !fClearList || SecurityContextListHead==NULL ); LeaveCriticalSection( &SecurityContextListCS ); } VOID Dns_FreeSecurityContextList( VOID ) /*++ Routine Description (): Free all security contexts in global list Arguments: None Return Value: None --*/ { PSEC_CNTXT pcur; PSEC_CNTXT ptmp; INT countDelete = 0; DNSDBG( SECURITY, ( "Dns_FreeSecurityContextList()\n" )); EnterCriticalSection( &SecurityContextListCS ); IF_DNSDBG( SECURITY ) { DnsDbg_SecurityContextList( "Before Get", SecurityContextListHead ); } // if empty list -- done if ( !SecurityContextListHead ) { DNSDBG( SECURITY, ( "Attempt to free empty SecurityCOntextList.\n" )); goto Done; } // // Cycle through list & free all entries // pcur = SecurityContextListHead->pNext; while( pcur ) { ptmp = pcur; pcur = pcur->pNext; Dns_FreeSecurityContext( ptmp ); countDelete++; } Done: SecContextDequeue += countDelete; LeaveCriticalSection( &SecurityContextListCS ); DNSDBG( SECURITY, ( "Dns_FreeSecurityContextList emptied %d entries\n", countDelete )); } VOID DnsPrint_SecurityContext( IN PRINT_ROUTINE PrintRoutine, IN OUT PPRINT_CONTEXT pPrintContext, IN LPSTR pszHeader, IN PSEC_CNTXT pSecCtxt ) { PSEC_CNTXT pctxt = (PSEC_CNTXT)pSecCtxt; if ( !pSecCtxt ) { PrintRoutine( pPrintContext, "%s NULL security context\n", pszHeader ? pszHeader : "" ); return; } DnsPrint_Lock(); PrintRoutine( pPrintContext, "%s\n" "\tptr = %p\n" "\tpnext = %p\n" "\tkey = %s %s %s\n" "\tversion = %d\n" "\tCred Handle = %p %p\n" "\tSec Handle = %p %p\n" "\tcreate time = %d\n" "\texpire time = %d\n" "\tcleanup time = %d\n" "\thave cred = %d\n" "\thave sec = %d\n" "\tnew con = %d\n" "\tinitialized = %d\n" "\techo token = %d\n", pszHeader ? pszHeader : "Security context:", pctxt, pctxt->pNext, IP_STRING(pctxt->Key.IpRemote), pctxt->Key.pszTkeyName, pctxt->Key.pszClientContext, pctxt->Version, pctxt->CredHandle.dwUpper, pctxt->CredHandle.dwLower, pctxt->hSecHandle.dwUpper, pctxt->hSecHandle.dwLower, pctxt->dwCreateTime, pctxt->dwExpireTime, pctxt->dwCleanupTime, pctxt->fHaveCredHandle, pctxt->fHaveSecHandle, pctxt->fNewConversation, pctxt->fNegoComplete, pctxt->fEchoToken ); if ( !pctxt->fHaveCredHandle ) { PrintRoutine( pPrintContext, "Global cred handle\n" "\tCred Handle = %p %p\n", g_hSspiCredentials.dwUpper, g_hSspiCredentials.dwLower ); } DnsPrint_Unlock(); } VOID DnsPrint_SecurityContextList( IN PRINT_ROUTINE PrintRoutine, IN OUT PPRINT_CONTEXT pPrintContext, IN LPSTR pszHeader, IN PSEC_CNTXT pList ) { PSEC_CNTXT pcur; EnterCriticalSection( &SecurityContextListCS ); DnsPrint_Lock(); pcur = pList; PrintRoutine( pPrintContext, "Security context list %s\n" "\tList ptr = %p\n" "%s", pszHeader, pList, pcur ? "" : "\tList EMPTY\n" ); while ( pcur != NULL ) { DnsPrint_SecurityContext( PrintRoutine, pPrintContext, NULL, pcur ); pcur = pcur->pNext; } PrintRoutine( pPrintContext, "*** End security context list ***\n" ); DnsPrint_Unlock(); LeaveCriticalSection( &SecurityContextListCS ); } // // Security utils // DNS_STATUS MakeKerberosName( OUT PWSTR pwsKerberosName, IN PSTR pszDnsName, IN BOOL fTrySpn ) /*++ Routine Description: Map DNS name to kerberos name for security lookup. Arguments: pszDnsName -- DNS name pwsKerberosName -- buffer to recv kerb name fSPNFormat -- use SPN format Return Value: ERROR_SUCCESS successful. ErrorCode on failure. --*/ { DNS_STATUS status = ERROR_SUCCESS; WCHAR nameBuf[ DNS_MAX_NAME_BUFFER_LENGTH ]; INT nameLength; PWCHAR pwMachine; PWCHAR pwDomain; PWCHAR pwTmp; DNSDBG( SECURITY, ( "MakeKerberosName(%s, %p, %d)\n", pszDnsName, pwsKerberosName, fTrySpn )); if ( !pszDnsName || !pwsKerberosName ) { DNS_ASSERT( FALSE ); return ERROR_INVALID_PARAMETER; } // // convert to wide char // - note, function returns byte count, not status // if ( ! Dns_NameCopyWireToUnicode( nameBuf, pszDnsName ) ) { status = GetLastError(); DNSDBG( SECURITY, ( "ERROR: Bad DNS name %s failed conversion to unicode\n", pszDnsName )); DNS_ASSERT( FALSE ); goto Cleanup; } // // build SPN name // if ( fTrySpn && g_pfnMakeSpn ) { nameLength = MAX_PATH; status = (DNS_STATUS) g_pfnMakeSpn( DNS_SPN_SERVICE_CLASS_W, nameBuf, & nameLength, pwsKerberosName ); DNSDBG( SECURITY, ( "Translated (via DsSpn) %s into Kerberos name: %S\n", pszDnsName, pwsKerberosName )); goto Cleanup; } // // no SPN -- build kerberos name // - convert FQDN to domain\machine$ // compatible with old servers that did not register SPNs. // { PWSTR pdomain; PWSTR pdump; // // break into host\domain name pieces // pdomain = Dns_GetDomainName_W( nameBuf ); if ( !pdomain ) { status = ERROR_INVALID_DATA; goto Cleanup; } *(pdomain-1) = 0; // break off single label domain name pdump = Dns_GetDomainName_W( pdomain ); if ( !pdump ) { status = ERROR_INVALID_DATA; goto Cleanup; } *(pdump-1) = 0; // format as \$ wcscpy( pwsKerberosName, pdomain ); wcscat( pwsKerberosName, L"\\" ); wcscat( pwsKerberosName, nameBuf ); wcscat( pwsKerberosName, L"$" ); // // note: tried this and got linker error // wsprintfW( pwsKerberosName, L"%S\\%ws$", pdomain, nameBuf ); } DNSDBG( SECURITY, ( "Translated %s into Kerberos name: %S\n", pszDnsName, pwsKerberosName )); Cleanup: return status; } DNS_STATUS Dns_LoadNtdsapiProcs( VOID ) /*++ Routine Description: Dynamically loads SPN function from Ntdsapi.dll Arguments: None Return Value: ERROR_SUCCESS if successful. ErrorCode on failure. --*/ { HMODULE hlib = NULL; DNS_STATUS status = ERROR_SUCCESS; // // Note, function assumes MT safe. // At single thread startup or protected by CS // // // return if module already loaded // if ( g_hLibNtdsa ) { ASSERT( g_pfnMakeSpn ); return ERROR_SUCCESS; } // // load ntdsapi.dll -- for getting SPNs // hlib = LoadLibraryExW( NTDSAPI_DLL_NAMEW, NULL, 0 ); // Previously used: DONT_RESOLVE_DLL_REFERENCES if ( !hlib ) { return GetLastError(); } // // get SPN function // g_pfnMakeSpn = GetProcAddress( hlib, MAKE_SPN_FUNC ); if ( !g_pfnMakeSpn ) { status = GetLastError(); FreeLibrary( hlib ); } else { g_hLibNtdsa = hlib; } return ERROR_SUCCESS; } DNS_STATUS Dns_StartSecurity( IN BOOL fProcessAttach ) /*++ Routine Description: Initialize the security package for dynamic update. Note, this function is self-initializing, BUT is not MT safe, unless called at process attach. Parameters: fProcessAttach - TRUE if called during process attach in that case we initialize only the CS otherwise we initialize completely Return Value: TRUE if successful. FALSE otherwise, error code available from GetLastError(). --*/ { DNS_STATUS status = ERROR_SUCCESS; static BOOL fcsInitialized = FALSE; // // DCR_PERF: ought to have one CS for dnslib, initialized on a DnsLib // init function; then it is always valid and can be used // whenever necessary // if ( fProcessAttach || !fcsInitialized ) { fcsInitialized = TRUE; InitializeCriticalSection( &SecurityContextListCS ); SecInvalidateHandle( &g_hSspiCredentials ); g_fSecurityPackageInitialized = FALSE; } // // do full security package init // if ( !fProcessAttach ) { EnterCriticalSection( &SecurityContextListCS ); if ( !g_fSecurityPackageInitialized ) { status = Dns_InitializeSecurityPackage( &g_SecurityTokenMaxLength, FALSE // client, not DNS server ); if ( status == ERROR_SUCCESS ) { g_fSecurityPackageInitialized = TRUE; // load ntdsapi.dll for SPN building status = Dns_LoadNtdsapiProcs(); ASSERT( ERROR_SUCCESS == status ); } } LeaveCriticalSection( &SecurityContextListCS ); } return( status ); } DNS_STATUS Dns_StartServerSecurity( VOID ) /*++ Routine Description: Startup server security. Note this function is NOT MT-safe. Call it once on load, or protect call with a CS. Arguments: None. Return Value: TRUE if security is initialized. FALSE if security initialization failure. --*/ { DNS_STATUS status; if ( g_fSecurityPackageInitialized ) { return( ERROR_SUCCESS ); } // // init globals // - this protects us on server restart // g_SecurityTokenMaxLength = 0; g_SignatureMaxLength = 0; SecurityContextListHead = NULL; g_pfnMakeSpn = NULL; // // CS is initialized before init sec pak in order to // have it done similarly to the client code. // InitializeCriticalSection( &SecurityContextListCS ); status = Dns_InitializeSecurityPackage( &g_SecurityTokenMaxLength, TRUE ); if ( status == ERROR_SUCCESS ) { g_fSecurityPackageInitialized = TRUE; } else { ASSERT ( g_fSecurityPackageInitialized == FALSE ); DeleteCriticalSection( &SecurityContextListCS ); } return( status ); } DNS_STATUS Dns_InitializeSecurityPackage( OUT PDWORD pdwMaxMessage, IN BOOL fDnsServer ) /*++ Routine Description: Load and initialize the security package. Note, call this function at first UPDATE. MUST NOT call this function at DLL init, this becomes possibly cyclic. Parameters: pdwMaxMessage - addr to recv max security token length Return Value: ERROR_SUCCESS if successful. ErrorCode on failure. --*/ { SECURITY_STATUS status; FARPROC psecurityEntry; PSecPkgInfoW pkgInfo; UUID uuid; // // init SSPI credentials handle (regardless of package state) // SecInvalidateHandle( &g_hSspiCredentials ); // // load and initialize the appropriate SSP // g_hLibSecurity = LoadLibrary( NT_DLL_NAME ); if ( !g_hLibSecurity ) { status = GetLastError(); DNS_PRINT(( "Couldn't load dll: %u\n", status )); goto Failed; } psecurityEntry = GetProcAddress( g_hLibSecurity, SECURITY_ENTRYPOINTW ); if ( !psecurityEntry ) { status = GetLastError(); DNS_PRINT(( "Couldn't get sec init routine: %u\n", status )); goto Failed; } g_pSecurityFunctionTable = (PSecurityFunctionTableW) psecurityEntry(); if ( !g_pSecurityFunctionTable ) { status = ERROR_DLL_INIT_FAILED; DNS_PRINT(( "ERROR: unable to get security function table.\n")); goto Failed; } // Get info for security package (negotiate) // - need max size of tokens status = g_pSecurityFunctionTable->QuerySecurityPackageInfoW( PACKAGE_NAME, &pkgInfo ); if ( !SEC_SUCCESS(status) ) { DNS_PRINT(( "Couldn't query package info for %s, error %u\n", PACKAGE_NAME, status )); goto Failed; } g_SecurityTokenMaxLength = pkgInfo->cbMaxToken; g_pSecurityFunctionTable->FreeContextBuffer( pkgInfo ); // // Note: This is the maximum addition to the size of the // DNS update packet. (excluding the signature) // // DCR_CLEANUP: what is the point of this? as we have set a global // if ( pdwMaxMessage) { *pdwMaxMessage = g_SecurityTokenMaxLength; } // // Acquire process credentials handle from SSPI // status = Dns_RefreshSSpiCredentialsHandle( fDnsServer, NULL ); if ( !SEC_SUCCESS(status) ) { DNSDBG( SECURITY, ( "Error 0xX: Cannot acquire credentials handle\n", status )); ASSERT ( FALSE ); goto Failed; } // // Get a unique id // - even if call fails, just take what's in stack // and make a string out of it -- we just want the string // UuidCreateSequential( &uuid ); DnsStringPrint_Guid( g_ContextUuid, & uuid ); DNSDBG( SECURITY, ( "Started security package (%S)\n" "\tmax token = %d\n", PACKAGE_NAME, g_SecurityTokenMaxLength )); return( ERROR_SUCCESS ); Failed: if ( status == ERROR_SUCCESS ) { status = ERROR_DLL_INIT_FAILED; } return( status ); } VOID Dns_TerminateSecurityPackage( VOID ) /*++ Routine Description: Terminate security package on shutdown. Arguments: None. Return Value: None. --*/ { DWORD status=ERROR_SUCCESS; if ( g_fSecurityPackageInitialized ) { #if 0 // // it turns out that the security lib get unloaded before in some cases // us for some reason (alhtough we explicity tells it to unload // after us). // We will never alloc over ourselves anyway (see startup). // if ( !SSPI_INVALID_HANDLE ( &g_hSspiCredentials ) ) { // // Free previously allocated handle // status = g_pSecurityFunctionTable->FreeCredentialsHandle( &g_hSspiCredentials ); if ( !SEC_SUCCESS(status) ) { DNSDBG( SECURITY, ( "Error <0x%x>: Cannot free credentials handle\n", status )); } } // continue regardless. SecInvalidateHandle( &g_hSspiCredentials ); Dns_FreeSecurityContextList(); #endif if ( g_hLibSecurity ) { FreeLibrary( g_hLibSecurity ); } if ( g_hLibNtdsa ) { FreeLibrary( g_hLibNtdsa ); } } DeleteCriticalSection( &SecurityContextListCS ); } DNS_STATUS Dns_InitClientSecurityContext( IN OUT PSECPACK pSecPack, IN LPSTR pszNameServer, OUT PBOOL pfDoneNegotiate ) /*++ Routine Description: Initialize client security context building security token to send. On first pass, creates context blob (and returns handle). On second pass, uses server context to rebuild negotiated token. Arguments: pSecPack -- ptr to security info for packet pszNameServer -- DNS server to nego with pCreds -- credentials (if given) pfDoneNegotiate -- addr to set if done with negotiation TRUE if done with nego FALSE if continuing Return Value: ERROR_SUCCESS -- if done DNS_STATUS_CONTINUE_NEEDED -- if continue respone to client is needed ErrorCode on failure. --*/ { //PSECPACK pSecPack = (PSECPACK)hSecPack; SECURITY_STATUS status; PSEC_CNTXT psecCtxt; BOOL fcreatedContext = FALSE; TimeStamp lifetime; SecBufferDesc outBufDesc; SecBufferDesc inBufDesc; ULONG contextAttributes = 0; WCHAR wszKerberosName[ MAX_PATH ]; PCredHandle pcredHandle; DNSDBG( SECURITY, ( "Enter InitClientSecurityContext()\n" )); IF_DNSDBG( SECURITY ) { DnsDbg_SecurityPacketInfo( "InitClientSecurityContext() at top.\n", pSecPack ); } // // if not existing context, create new one // // note: if want to create new here, then need context key // psecCtxt = pSecPack->pSecContext; if ( !psecCtxt ) { DNSDBG( SECURITY, ( "ERROR: Called into Dns_InitClientSecurityContext w/ no security context!!\n" )); ASSERT ( FALSE ); return( DNS_ERROR_NO_MEMORY ); } // // client completed initialization // - if server sent back token, should be echo of client's token // if ( psecCtxt->fNegoComplete ) { if ( pSecPack->LocalBuf.pvBuffer && pSecPack->LocalBuf.cbBuffer == pSecPack->RemoteBuf.cbBuffer && RtlEqualMemory( pSecPack->LocalBuf.pvBuffer, pSecPack->RemoteBuf.pvBuffer, pSecPack->LocalBuf.cbBuffer ) ) { return( ERROR_SUCCESS ); } DNSDBG( ANY, ( "InitClientSecurityContext() on already negotiated context %p\n" "\tserver buffer is NOT echo of buffer sent!\n", psecCtxt )); return( DNS_ERROR_RCODE_BADKEY ); } // // prepare output buffer, allocate if necessary // - security token will be written to this buffer // if ( !pSecPack->LocalBuf.pvBuffer ) { PCHAR pbuf; ASSERT( g_SecurityTokenMaxLength ); pbuf = (PVOID) ALLOCATE_HEAP( g_SecurityTokenMaxLength ); if ( !pbuf ) { status = DNS_ERROR_NO_MEMORY; goto Failed; } pSecPack->LocalBuf.pvBuffer = pbuf; pSecPack->LocalBuf.BufferType = SECBUFFER_TOKEN; //pSecPack->LocalBuf.cbBuffer = g_SecurityTokenMaxLength; } // set\reset buffer length pSecPack->LocalBuf.cbBuffer = g_SecurityTokenMaxLength; outBufDesc.ulVersion = 0; outBufDesc.cBuffers = 1; outBufDesc.pBuffers = &pSecPack->LocalBuf; // DCR_PERF: zeroing buffer is unnecessary -- remove RtlZeroMemory( pSecPack->LocalBuf.pvBuffer, pSecPack->LocalBuf.cbBuffer ); // // if have response from server, then send as input buffer // if ( pSecPack->RemoteBuf.pvBuffer ) { ASSERT( !psecCtxt->fNewConversation ); ASSERT( pSecPack->RemoteBuf.cbBuffer ); ASSERT( pSecPack->RemoteBuf.BufferType == SECBUFFER_TOKEN ); inBufDesc.ulVersion = 0; inBufDesc.cBuffers = 1; inBufDesc.pBuffers = & pSecPack->RemoteBuf; } ELSE_ASSERT( psecCtxt->fNewConversation ); // // get server in SPN format // // DCR_PERF: SPN name lookup duplicated on second pass // - if know we are synchronous could keep // - or could save to packet stuct (but then would have to alloc) status = MakeKerberosName( wszKerberosName, pszNameServer, TRUE ); if ( status != ERROR_SUCCESS ) { status = ERROR_INVALID_DATA; goto Failed; } IF_DNSDBG( SECURITY ) { DNS_PRINT(( "Before InitClientSecurityContextW().\n" "\ttime (ms) = %d\n" "\tkerb name = %S\n", GetCurrentTime(), wszKerberosName )); DnsDbg_SecurityPacketInfo( "Before call to InitClientSecurityContextW().\n", pSecPack ); } // // cred handle // pcredHandle = &g_hSspiCredentials; if ( psecCtxt->fHaveCredHandle ) { pcredHandle = &psecCtxt->CredHandle; } status = g_pSecurityFunctionTable->InitializeSecurityContextW( pcredHandle, psecCtxt->fNewConversation ? NULL : &psecCtxt->hSecHandle, wszKerberosName, ISC_REQ_REPLAY_DETECT | ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH, // context requirements 0, // reserved1 SECURITY_NATIVE_DREP, psecCtxt->fNewConversation ? NULL : &inBufDesc, 0, // reserved2 & psecCtxt->hSecHandle, & outBufDesc, & contextAttributes, & lifetime ); DNSDBG( SECURITY, ( "After InitClientSecurityContextW().\n" "\ttime (ms) = %d\n" "\tkerb name = %S\n" "\tcontext attr = %08x\n" "\tstatus = %d (%08x)\n", GetCurrentTime(), wszKerberosName, contextAttributes, status, status )); // // failed? // - if unable to get kerberos (mutual auth) then bail // this eliminates trying to do nego when in workgroup // if ( !SEC_SUCCESS(status) || ( status == SEC_E_OK && !(contextAttributes & ISC_REQ_MUTUAL_AUTH) ) ) { DNS_PRINT(( "InitializeSecurityContextW() failed: %08x %u\n" "\tContext Attributes = %p\n" "\tTokenMaxLength = %d\n" "\tSigMaxLength = %d\n" "\tPackageInitialized = %d\n" "\tlifetime = %d\n", status, status, contextAttributes, g_SecurityTokenMaxLength, g_SignatureMaxLength, g_fSecurityPackageInitialized, lifetime )); // // DCR: security error codes on local function failures: // - key's no good // - sigs no good // RCODE errors are fine for sending back to remote, but don't // convey the correct info locally // status = DNS_ERROR_RCODE_BADKEY; goto Failed; } psecCtxt->fHaveSecHandle = TRUE; DNSDBG( SECURITY, ( "Finished InitializeSecurityContext():\n" "\tstatus = %08x (%d)\n" "\thandle = %p\n" "\toutput buffers\n" "\t\tcBuffers = %d\n" "\t\tpBuffers = %p\n" "\tlocal buffer\n" "\t\tptr = %p\n" "\t\tlength = %d\n", status, status, & psecCtxt->hSecHandle, outBufDesc.cBuffers, outBufDesc.pBuffers, pSecPack->LocalBuf.pvBuffer, pSecPack->LocalBuf.cbBuffer )); ASSERT( status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE ); // // determine signature length // // note: not safe to do just once on start of process, as can fail // to locate DC and end up ntlm on first pass then locate // DC later and need a larger sig; so many potential client's // under services, it is dangerous not to calculate each time // if ( status == SEC_E_OK ) { SecPkgContext_Sizes Sizes; status = g_pSecurityFunctionTable->QueryContextAttributesW( & psecCtxt->hSecHandle, SECPKG_ATTR_SIZES, (PVOID) &Sizes ); if ( !SEC_SUCCESS(status) ) { // DEVNOTE: this will leave us will valid return but // potentially unset sig max length goto Failed; } if ( Sizes.cbMaxSignature > g_SignatureMaxLength ) { g_SignatureMaxLength = Sizes.cbMaxSignature; } DNSDBG( SECURITY, ( "Signature max length = %d\n", g_SignatureMaxLength )); } // // now have context, flag for next pass // psecCtxt->fNewConversation = FALSE; // // completed -- have key // - if just created, then need to send back to server // - otherwise done // if ( status == ERROR_SUCCESS ) { psecCtxt->fNegoComplete = TRUE; ASSERT( pSecPack->LocalBuf.pvBuffer ); if ( pSecPack->LocalBuf.cbBuffer ) { //ASSERT( pSecPack->LocalBuf.cbBuffer != pSecPack->RemoteBuf.cbBuffer ); status = DNS_STATUS_CONTINUE_NEEDED; } } // // continue needed? -- use single return code // else { ASSERT( status == SEC_I_CONTINUE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE ); DNSDBG( SECURITY, ( "Initializing client context continue needed.\n" "\tlocal complete = %d\n", ( status == SEC_I_COMPLETE_AND_CONTINUE ) )); //psecCtxt->State = DNSGSS_STATE_CONTINUE; status = DNS_STATUS_CONTINUE_NEEDED; psecCtxt->fNegoComplete = FALSE; } *pfDoneNegotiate = psecCtxt->fNegoComplete; ASSERT( status == ERROR_SUCCESS || status == DNS_STATUS_CONTINUE_NEEDED ); Failed: IF_DNSDBG( SECURITY ) { DnsPrint_Lock(); DNSDBG( SECURITY, ( "Leaving InitClientSecurityContext().\n" "\tstatus = %08x (%d)\n", status, status )); DnsDbg_SecurityContext( "Security Context", psecCtxt ); DnsDbg_SecurityPacketInfo( "Security Session Packet Info", pSecPack ); DnsPrint_Unlock(); } #if 0 // // security context (the struct) is NEVER created in this function // so no need to determine cleanup issue on failure // caller determines action if // if ( status == ERROR_SUCCESS || status == DNS_STATUS_CONTINUE_NEEDED ) { return( status ); } // // DEVNOTE: should we attempt to preserve a context on failure? // - could be a potential security attack to crash negotiation contexts, // by sending garbage // - however don't want bad context to stay around and block all future // attempts to renegotiate // // delete any locally create context // caller will be responsible for making determination about recaching or // deleting context for passed in context // if ( fcreatedContext ) { Dns_FreeSecurityContext( psecCtxt ); pSecPack->pSecContext = NULL; } else { Dns_EnlistSecurityContext( (PSEC_CNTXT)psecCtxt ); } #endif return( status ); } DNS_STATUS Dns_ServerAcceptSecurityContext( IN OUT PSECPACK pSecPack, IN BOOL fBreakOnAscFailure ) /*++ Routine Description: Initialized server's security context for session with client. This is called with newly created context on first client packet, then called again with previously initialized context, after client responds to negotiation. Arguments: pSecPack -- security context info for server's session with client Return Value: ERROR_SUCCESS -- if done DNS_STATUS_CONTINUE_NEEDED -- if continue respone to client is needed ErrorCode on failure. --*/ { PSEC_CNTXT psecCtxt; SECURITY_STATUS status; TimeStamp lifetime; SecBufferDesc outBufDesc; SecBufferDesc inBufDesc; ULONG contextAttributes = 0; DNSDBG( SECURITY, ( "ServerAcceptSecurityContext(%p, fBreak=%d)\n", pSecPack, fBreakOnAscFailure )); IF_DNSDBG( SECURITY ) { DnsDbg_SecurityPacketInfo( "Entering ServerAcceptSecurityContext()", pSecPack ); } // // get context // psecCtxt = pSecPack->pSecContext; if ( !psecCtxt ) { DNSDBG( SECURITY, ( "ERROR: ServerAcceptSecurityContext called with no security context\n" )); ASSERT( FALSE ); return( DNS_ERROR_NO_MEMORY ); } // // already initialized // - echo of previous token is legitimate // - if client still thinks it's negotiating => problem // // DCR_CLEAN: need clear story here on how to handle this -- do these // "mistaken" clients cause context to be scrapped from cache? // if ( psecCtxt->fNegoComplete ) { if ( psecCtxt->TkeySize == pSecPack->RemoteBuf.cbBuffer ) { return( ERROR_SUCCESS ); } #if 0 // DCR_FIX: // NOTE: couldn't do buf compare as not MT // safe when allow context\buffer cleanup // QUESTION: how can this be dumped while in use if ( pSecPack->LocalBuf.pvBuffer && psecCtxt->TkeySize == pSecPack->RemoteBuf.cbBuffer && pSecPack->LocalBuf.cbBuffer == pSecPack->RemoteBuf.cbBuffer && RtlEqualMemory( pSecPack->LocalBuf.pvBuffer, pSecPack->RemoteBuf.pvBuffer, pSecPack->LocalBuf.cbBuffer ) ) { return( ERROR_SUCCESS ); } #endif DNSDBG( ANY, ( "WARNING: Server receiving new or incorrect TKEY on already\n" "\tnegotiated context %p;\n" "\tserver buffer is NOT echo of buffer sent!\n", psecCtxt )); return( DNS_ERROR_RCODE_BADKEY ); } // refresh SSPI credentials if expired if ( SSPI_EXPIRED_HANDLE( g_SspiCredentialsLifetime ) ) { status = Dns_RefreshSSpiCredentialsHandle( TRUE, NULL ); if ( !SEC_SUCCESS(status) ) { DNS_PRINT(( "Error <0x%x>: Cannot refresh Sspi Credentials Handle\n", status )); } } // // accept security context // // allocate local token buffer if doesn't exists // note, the reason I do this is so I won't have the memory of // a large buffer sitting around during a two pass security session // and hence tied up until I time out // // DCR_PERF: security token buffer allocation // since context will be verified before queued, is this approach // sensible? // if can delete when TCP connection fails, or on short timeout, then // ok to append to SEC_CNTXT and save an allocation // if ( !pSecPack->LocalBuf.pvBuffer ) { PCHAR pbuf; pbuf = (PVOID) ALLOCATE_HEAP( g_SecurityTokenMaxLength ); if ( !pbuf ) { status = DNS_ERROR_NO_MEMORY; goto Failed; } pSecPack->LocalBuf.pvBuffer = pbuf; pSecPack->LocalBuf.cbBuffer = g_SecurityTokenMaxLength; pSecPack->LocalBuf.BufferType = SECBUFFER_TOKEN; } pSecPack->LocalBuf.cbBuffer = g_SecurityTokenMaxLength; outBufDesc.ulVersion = 0; outBufDesc.cBuffers = 1; outBufDesc.pBuffers = &pSecPack->LocalBuf; // DCR_PERF: zeroing nego buffer is unnecessary RtlZeroMemory( pSecPack->LocalBuf.pvBuffer, pSecPack->LocalBuf.cbBuffer ); // prepare input buffer with client token inBufDesc.ulVersion = 0; inBufDesc.cBuffers = 1; inBufDesc.pBuffers = & pSecPack->RemoteBuf; status = g_pSecurityFunctionTable->AcceptSecurityContext( & g_hSspiCredentials, psecCtxt->fNewConversation ? NULL : & psecCtxt->hSecHandle, & inBufDesc, ASC_REQ_REPLAY_DETECT | ASC_REQ_DELEGATE | ASC_REQ_MUTUAL_AUTH, // context requirements SECURITY_NATIVE_DREP, & psecCtxt->hSecHandle, & outBufDesc, & contextAttributes, & lifetime ); if ( fBreakOnAscFailure && ( status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED && status != SEC_I_COMPLETE_AND_CONTINUE ) ) { DNS_PRINT(( "HARD BREAK: BreakOnAscFailure status=%d\n", status )); DebugBreak(); } if ( !SEC_SUCCESS(status) ) { DNS_PRINT(( "ERROR: Accept security context failed status = %d (%08x)\n", status, status )); goto Failed; } psecCtxt->fHaveSecHandle = TRUE; DNSDBG( SECURITY, ( "Finished AcceptSecurityContext():\n" "\tstatus = %08x (%d)\n" "\thandle = %p\n" "\toutput buffers\n" "\t\tcBuffers = %d\n" "\t\tpBuffers = %p\n" "\tlocal buffer\n" "\t\tptr = %p\n" "\t\tlength = %d\n" "\tlifetime = %ld %ld\n" "\tcontext flag = 0x%lx\n", status, status, & psecCtxt->hSecHandle, outBufDesc.cBuffers, outBufDesc.pBuffers, pSecPack->LocalBuf.pvBuffer, pSecPack->LocalBuf.cbBuffer, lifetime.HighPart, lifetime.LowPart, contextAttributes )); ASSERT( status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE ); // // compute the size of signature if you are done with initializing // the security context and haven't done it before // if ( status == SEC_E_OK ) { SecPkgContext_Sizes Sizes; // // reject NULL sessions // NTLM security will establish NULL sessions to non-domain clients, // even if ASC_REQ_ALLOW_NULL_SESSION is not set // note, context has been created, but will be cleaned up in normal // failure path // if ( contextAttributes & ASC_RET_NULL_SESSION ) { DNSDBG( SECURITY, ( "Rejecting NULL session from AcceptSecurityContext()\n" )); status = DNS_ERROR_RCODE_BADKEY; goto Failed; } status = g_pSecurityFunctionTable->QueryContextAttributesW( &psecCtxt->hSecHandle, SECPKG_ATTR_SIZES, (PVOID)& Sizes ); if ( !SEC_SUCCESS(status) ) { DNS_PRINT(( "Query context attribtues failed\n" )); ASSERT( FALSE ); goto Failed; } // // we should use the largest signature there is among all // packages // // DCR_FIX: signature length stuff bogus??? // // when packet is signed, the length is assumed to be g_SignatureMaxLength // if this is not the signature length for the desired package, does // this still work properly??? // // DCR_FIX: potential very small timing window where two clients // getting different packages could cause this to miss highest // value -- potential causing a signing failure? // if ( Sizes.cbMaxSignature > g_SignatureMaxLength ) { g_SignatureMaxLength = Sizes.cbMaxSignature; } // // finished negotiation // - set flag // - save final TKEY data length, so can recognize response // // this is valid only on new conversation, shouldn't have // no sig second time through // psecCtxt->fNegoComplete = TRUE; psecCtxt->TkeySize = (WORD) pSecPack->LocalBuf.cbBuffer; // // need token response from server // some protocols (kerberos) complete in one pass, but hence require // non-echo response from server for mutual-authentication // if ( psecCtxt->TkeySize ) { DNSDBG( SECURITY, ( "Successful security context accept, but need server reponse\n" "\t-- doing continue.\n" )); status = DNS_STATUS_CONTINUE_NEEDED; } #if 0 if ( !psecCtxt->pTsigRR && psecCtxt->fNewConversation ) { DNSDBG( SECURITY, ( "Successful security context accept, without sig, doing continue\n" )); status = DNS_STATUS_CONTINUE_NEEDED; } #endif } // // continue needed? // - single status code returned for continue needed // else if ( status == SEC_I_CONTINUE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE ) { DNSDBG( SECURITY, ( "Initializing server context, continue needed.\n" "\tlocal complete = %d\n", ( status == SEC_I_COMPLETE_AND_CONTINUE ) )); psecCtxt->fNegoComplete = FALSE; status = DNS_STATUS_CONTINUE_NEEDED; } psecCtxt->fNewConversation = FALSE; Failed: IF_DNSDBG( SECURITY ) { DNSDBG( SECURITY, ( "Leaving ServerAcceptSecurityContext().\n" "\tstatus = %d %08x\n", status, status )); DnsDbg_SecurityContext( "Security Session Context leaving ServerAcceptSecurityContext()", psecCtxt ); } return( status ); } DNS_STATUS Dns_SrvImpersonateClient( IN HANDLE hSecPack ) /*++ Routine Description: Make server impersonate client. Parameters: hSecPack -- session context handle Return Value: ERROR_SUCCESS if successful impersonation. ErrorCode on failue. --*/ { PSEC_CNTXT psecCtxt; // get security context psecCtxt = ((PSECPACK)hSecPack)->pSecContext; if ( !psecCtxt ) { DNS_PRINT(( "ERROR: Dns_SrvImpersonateClient without context!!!\n" )); ASSERT( FALSE ); return( DNS_ERROR_RCODE_BADKEY ); } return g_pSecurityFunctionTable->ImpersonateSecurityContext( &psecCtxt->hSecHandle ); } DNS_STATUS Dns_SrvRevertToSelf( IN HANDLE hSecPack ) /*++ Routine Description: Return server context to itself. Parameters: hSecPack -- session context handle Return Value: ERROR_SUCCESS if successful impersonation. ErrorCode on failue. --*/ { PSEC_CNTXT psecCtxt; // get security context psecCtxt = ((PSECPACK)hSecPack)->pSecContext; if ( !psecCtxt ) { DNS_PRINT(( "ERROR: Dns_SrvRevertToSelf without context!!!\n" )); ASSERT( FALSE ); return( DNS_ERROR_RCODE_BADKEY ); } return g_pSecurityFunctionTable->RevertSecurityContext( &psecCtxt->hSecHandle ); } // // Security record packet write // DNS_STATUS Dns_WriteGssTkeyToMessage( IN PSECPACK pSecPack, IN PDNS_HEADER pMsgHead, IN PCHAR pMsgBufEnd, IN OUT PCHAR * ppCurrent, IN BOOL fIsServer ) /*++ Routine Description: Write security record into packet, and optionally sign. Arguments: hSecPack -- security session handle pMsgHead -- ptr to start of DNS message pMsgEnd -- ptr to end of message buffer ppCurrent -- addr to recv ptr to end of message fIsServer -- performing this operation as DNS server? Return Value: ERROR_SUCCESS on success ErrorCode of failure to accomodate or sign message. --*/ { DNS_STATUS status = ERROR_INVALID_DATA; PSEC_CNTXT psecCtxt; PCHAR pch; DWORD expireTime; WORD keyLength; WORD keyRecordDataLength; PCHAR precordData; PCHAR pnameAlg; WORD lengthAlg; DNSDBG( SECURITY, ( "Dns_WriteGssTkeyToMessage( %p )\n", pSecPack )); // // get security context // psecCtxt = pSecPack->pSecContext; if ( !psecCtxt ) { DNS_PRINT(( "ERROR: attempted signing without security context!!!\n" )); ASSERT( FALSE ); return( DNS_ERROR_RCODE_BADKEY ); } // // peal packet back to question section // pMsgHead->AnswerCount = 0; pMsgHead->NameServerCount = 0; pMsgHead->AdditionalCount = 0; // go to end of packet to insert TKEY record pch = Dns_SkipToRecord( pMsgHead, pMsgBufEnd, 0 // go to end of packet ); if ( !pch ) { DNS_ASSERT( FALSE ); DNS_PRINT(("Dns_SkipToSecurityRecord failed!\n" )); goto Exit; } // // reset section count where the TKEY RR will be written // // for client section depends on version // W2K -> answer // later -> additional // if ( fIsServer ) { pMsgHead->AnswerCount = 1; // for server set client TKEY version in context // - if not learned on previous pass if ( psecCtxt->Version == 0 ) { psecCtxt->Version = pSecPack->TkeyVersion; } } else { if ( psecCtxt->Version == TKEY_VERSION_W2K ) { pMsgHead->AnswerCount = 1; } else { pMsgHead->AdditionalCount = 1; } } // // write TKEY owner // - this is context "name" // pch = Dns_WriteDottedNameToPacket( pch, pMsgBufEnd, psecCtxt->Key.pszTkeyName, NULL, // FQDN, no domain 0, // no domain offset FALSE // not unicode ); if ( !pch ) { goto Exit; } // // TKEY record // - algorithm owner // - time // - expire time // - key length // - key // if ( psecCtxt->Version == TKEY_VERSION_W2K ) { pnameAlg = g_pAlgorithmNameW2K; lengthAlg = W2K_GSS_ALGORITHM_NAME_PACKET_LENGTH; } else { //DNS_ASSERT( psecCtxt->Version == TKEY_VERSION_CURRENT ); pnameAlg = g_pAlgorithmNameCurrent; lengthAlg = GSS_ALGORITHM_NAME_PACKET_LENGTH; } keyLength = (WORD) pSecPack->LocalBuf.cbBuffer; keyRecordDataLength = keyLength + SIZEOF_TKEY_FIXED_DATA + lengthAlg; if ( pch + sizeof(DNS_WIRE_RECORD) + keyRecordDataLength > pMsgBufEnd ) { DNS_PRINT(( "Dns_WriteGssTkeyToMessage() failed! -- insufficient length\n" )); DNS_ASSERT( FALSE ); status = ERROR_INVALID_PARAMETER; goto Exit; } pch = Dns_WriteRecordStructureToPacketEx( pch, DNS_TYPE_TKEY, DNS_CLASS_ANY, 0, keyRecordDataLength ); // write algorithm name precordData = pch; RtlCopyMemory( pch, pnameAlg, lengthAlg ); pch += lengthAlg; // time signed and expire time // give ten minutes before expiration expireTime = (DWORD) time( NULL ); INLINE_WRITE_FLIPPED_DWORD( pch, expireTime ); pch += sizeof(DWORD); expireTime += TKEY_EXPIRE_INTERVAL; INLINE_WRITE_FLIPPED_DWORD( pch, expireTime ); pch += sizeof(DWORD); // mode INLINE_WRITE_FLIPPED_WORD( pch, DNS_TKEY_MODE_GSS ); pch += sizeof(WORD); // extended RCODE -- report back to caller INLINE_WRITE_FLIPPED_WORD( pch, pSecPack->ExtendedRcode ); pch += sizeof(WORD); // key length INLINE_WRITE_FLIPPED_WORD( pch, keyLength ); pch += sizeof(WORD); // write key token RtlCopyMemory( pch, pSecPack->LocalBuf.pvBuffer, keyLength ); pch += keyLength; DNSDBG( SECURITY, ( "Wrote TKEY to packet at %p\n" "\tlength = %d\n" "\tpacket end = %p\n", pMsgHead, keyLength, pch )); ASSERT( pch < pMsgBufEnd ); // other length WRITE_UNALIGNED_WORD( pch, 0 ); pch += sizeof(WORD); ASSERT( pch < pMsgBufEnd ); ASSERT( pch - precordData == keyRecordDataLength ); *ppCurrent = pch; status = ERROR_SUCCESS; Exit: return( status ); } DNS_STATUS Dns_SignMessageWithGssTsig( IN HANDLE hSecPackCtxt, IN PDNS_HEADER pMsgHead, IN PCHAR pMsgBufEnd, IN OUT PCHAR * ppCurrent ) /*++ Routine Description: Write GSS TSIG record to packet. Arguments: hSecPackCtxt -- packet security context pMsgHead -- ptr to start of DNS message pMsgEnd -- ptr to end of message buffer ppCurrent -- addr to recv ptr to end of message Return Value: ERROR_SUCCESS on success ErrorCode of failure to accomodate or sign message. --*/ { PSECPACK pSecPack = (PSECPACK) hSecPackCtxt; PSEC_CNTXT psecCtxt; DNS_STATUS status = ERROR_INVALID_DATA; PCHAR pch; // ptr to walk through TSIG record during build PCHAR ptsigRRHead; PCHAR ptsigRdataBegin; PCHAR ptsigRdataEnd; PCHAR pbufStart = NULL; // signing buf PCHAR pbuf; // ptr to walk through signing buf PCHAR psig = NULL; // query signature WORD sigLength; DWORD length; DWORD createTime; SecBufferDesc outBufDesc; SecBuffer outBuffs[2]; WORD netXid; PCHAR pnameAlg; DWORD lengthAlg; DNSDBG( SECURITY, ( "Dns_SignMessageWithGssTsig( %p )\n", pMsgHead )); // // get security context // psecCtxt = pSecPack->pSecContext; if ( !psecCtxt ) { DNS_PRINT(( "ERROR: attempted signing without security context!!!\n" )); ASSERT( FALSE ); return( DNS_ERROR_RCODE_BADKEY ); } // // peal off existing TSIG (if any) // if ( pMsgHead->AdditionalCount ) { DNS_PARSED_RR parsedRR; pch = Dns_SkipToRecord( pMsgHead, pMsgBufEnd, (-1) // go to last record ); if ( !pch ) { DNS_ASSERT( FALSE ); DNS_PRINT(("Dns_SkipToRecord() failed!\n" )); goto Exit; } pch = Dns_ParsePacketRecord( pch, pMsgBufEnd, &parsedRR ); if ( !pch ) { DNS_ASSERT( FALSE ); DNS_PRINT(("Dns_ParsePacketRecord failed!\n" )); goto Exit; } if ( parsedRR.Type == DNS_TYPE_TSIG ) { DNSDBG( SECURITY, ( "Erasing existing TSIG before resigning packet %p\n", pMsgHead )); pMsgHead->AdditionalCount--; } // note could save end-of-message here (pch) // for non-TSIG case instead of redoing skip } // go to end of packet to insert TSIG record pch = Dns_SkipToRecord( pMsgHead, pMsgBufEnd, 0 // go to end of packet ); if ( !pch ) { DNS_ASSERT( FALSE ); DNS_PRINT(("Dns_SkipToSecurityRecord failed!\n" )); goto Exit; } // // write TSIG owner // - this is context "name" // pch = Dns_WriteDottedNameToPacket( pch, pMsgBufEnd, psecCtxt->Key.pszTkeyName, NULL, // FQDN, no domain 0, // no domain offset FALSE // not unicode ); if ( !pch ) { goto Exit; } // // TSIG record // - algorithm owner // - time // - expire time // - original XID // - sig length // - sig // if ( psecCtxt->Version == TKEY_VERSION_W2K ) { pnameAlg = g_pAlgorithmNameW2K; lengthAlg = W2K_GSS_ALGORITHM_NAME_PACKET_LENGTH; } else { //DNS_ASSERT( psecCtxt->Version == TKEY_VERSION_CURRENT ); pnameAlg = g_pAlgorithmNameCurrent; lengthAlg = GSS_ALGORITHM_NAME_PACKET_LENGTH; } if ( pch + sizeof(DNS_WIRE_RECORD) + SIZEOF_TSIG_FIXED_DATA + lengthAlg + g_SignatureMaxLength > pMsgBufEnd ) { DNS_PRINT(( "Dns_WriteTsigToMessage() failed! -- insufficient length\n" )); DNS_ASSERT( FALSE ); status = ERROR_INVALID_PARAMETER; goto Exit; } // write record structure ptsigRRHead = pch; pch = Dns_WriteRecordStructureToPacketEx( pch, DNS_TYPE_TSIG, DNS_CLASS_ANY, // per TSIG-04 draft 0, 0 ); // write algorithm name // - save ptr to RDATA as all is directly signable in packet // format up to SigLength field ptsigRdataBegin = pch; RtlCopyMemory( pch, pnameAlg, lengthAlg ); pch += lengthAlg; // // set time fields // - signing time seconds since 1970 in 48 bit // - expire time // // DCR_FIX: not 2107 safe // have 48 bits on wire, but setting with 32 bit time // RtlZeroMemory( pch, sizeof(WORD) ); pch += sizeof(WORD); createTime = (DWORD) time( NULL ); INLINE_WRITE_FLIPPED_DWORD( pch, createTime ); pch += sizeof(DWORD); INLINE_WRITE_FLIPPED_WORD( pch, TSIG_EXPIRE_INTERVAL ); pch += sizeof(WORD); ptsigRdataEnd = pch; // // create signing buffer // - everything signed must fit into message // pbuf = ALLOCATE_HEAP( MAX_SIGNING_SIZE ); if ( !pbuf ) { status = DNS_ERROR_NO_MEMORY; goto Exit; } pbufStart = pbuf; // // sign // - query signature (if exists) // (note, W2K improperly left out query sig length) // - message up to TSIG // - TSIG owner name // - TSIG header // - class // - TTL // - TSIG RDATA // - everything before SigLength // - original id // - other data length and other data // if ( pMsgHead->IsResponse ) { if ( pSecPack->pQuerySig ) { WORD sigLength = pSecPack->QuerySigLength; ASSERT( sigLength != 0 ); DNS_ASSERT( psecCtxt->Version != 0 ); if ( psecCtxt->Version >= TKEY_VERSION_XP_RC1 ) { DNSDBG( SECURITY, ( "New signing including query sig length =%x\n", sigLength )); INLINE_WRITE_FLIPPED_WORD( pbuf, sigLength ); pbuf += sizeof(WORD); } RtlCopyMemory( pbuf, pSecPack->pQuerySig, sigLength ); pbuf += sigLength; } // if server has just completed TKEY nego, it may sign response without query // otherwise no query sig is invalid for response else if ( !pSecPack->pTkeyRR ) { DNS_PRINT(( "ERROR: no query sig available when signing response at %p!!!\n", pMsgHead )); ASSERT( FALSE ); status = DNS_ERROR_RCODE_SERVER_FAILURE; goto Exit; } DNSDBG( SECURITY, ( "Signing TKEY response without query sig.\n" )); } // // copy message // - go right through, TSIG owner name // - message header MUST be in network order // - save XID in netorder, it is included in TSIG RR // DNS_BYTE_FLIP_HEADER_COUNTS( pMsgHead ); length = (DWORD)(ptsigRRHead - (PCHAR)pMsgHead); netXid = pMsgHead->Xid; RtlCopyMemory( pbuf, (PCHAR) pMsgHead, length ); pbuf += length; DNS_BYTE_FLIP_HEADER_COUNTS( pMsgHead ); // copy TSIG class (ANY) and TTL (0) WRITE_UNALIGNED_WORD( pbuf, DNS_RCLASS_ANY ); pbuf += sizeof(WORD); WRITE_UNALIGNED_DWORD( pbuf, 0 ); pbuf += sizeof(DWORD); // copy TSIG RDATA through sig length = (DWORD)(ptsigRdataEnd - ptsigRdataBegin); RtlCopyMemory( pbuf, ptsigRdataBegin, length ); pbuf += length; // copy extended RCODE -- report back to caller INLINE_WRITE_FLIPPED_WORD( pbuf, pSecPack->ExtendedRcode ); pbuf += sizeof(WORD); // copy other data length and other data // - currently just zero length field *pbuf++ = 0; *pbuf++ = 0; length = (DWORD)(pbuf - pbufStart); DNSDBG( SECURITY, ( "Copied %d bytes to TSIG signing buffer.\n", length )); // // sign the packet // buf[0] is data // buf[1] is signature // // note: we write signature DIRECTLY into the real packet buffer // ASSERT( pch + g_SignatureMaxLength <= pMsgBufEnd ); outBufDesc.ulVersion = 0; outBufDesc.cBuffers = 2; outBufDesc.pBuffers = outBuffs; outBuffs[0].pvBuffer = pbufStart; outBuffs[0].cbBuffer = length; outBuffs[0].BufferType = SECBUFFER_DATA; // | SECBUFFER_READONLY; outBuffs[1].pvBuffer = pch + sizeof(WORD); outBuffs[1].cbBuffer = g_SignatureMaxLength; outBuffs[1].BufferType = SECBUFFER_TOKEN; status = g_pSecurityFunctionTable->MakeSignature( & psecCtxt->hSecHandle, 0, & outBufDesc, 0 // sequence detection ); if ( status != SEC_E_OK && status != SEC_E_CONTEXT_EXPIRED && status != SEC_E_QOP_NOT_SUPPORTED ) { DNS_PRINT(( "MakeSignature() failed status = %08x (%d)\n", status, status )); goto Exit; } IF_DNSDBG( SECURITY ) { DnsPrint_Lock(); DnsDbg_MessageNoContext( "Signed packet", pMsgHead, (WORD) (pch - (PCHAR)pMsgHead) ); DNS_PRINT(( "Signing info:\n" "\tsign data buf %p\n" "\t length %d\n" "\tsignature buf %p (in packet)\n" "\t length %d\n", outBuffs[0].pvBuffer, outBuffs[0].cbBuffer, outBuffs[1].pvBuffer, outBuffs[1].cbBuffer )); DnsDbg_RawOctets( "Signing buffer:", NULL, outBuffs[0].pvBuffer, outBuffs[0].cbBuffer ); DnsDbg_RawOctets( "Signature:", NULL, outBuffs[1].pvBuffer, outBuffs[1].cbBuffer ); DnsPrint_Unlock(); } // // continue building packet TSIG RDATA // - siglength // - signature // - original id // - error code // - other length // - other data // // get signature length // set sig length in packet // // if this is query SAVE signature, to verify response // sigLength = (WORD) outBuffs[1].cbBuffer; INLINE_WRITE_FLIPPED_WORD( pch, sigLength ); pch += sizeof(WORD); // // client saves off signature sent, to use in hash on response // - server using client's sig in hash, blocks some attacks // if ( !pMsgHead->IsResponse ) { ASSERT( !pSecPack->pQuerySig ); psig = ALLOCATE_HEAP( sigLength ); if ( !psig ) { status = DNS_ERROR_NO_MEMORY; goto Exit; } RtlCopyMemory( psig, pch, sigLength ); pSecPack->pQuerySig = psig; pSecPack->QuerySigLength = sigLength; } // jump over signature -- it was directly written to packet pch += sigLength; // original id follows signature WRITE_UNALIGNED_WORD( pch, netXid ); //RtlCopyMemory( pch, (PCHAR)&netXid, sizeof(WORD) ); pch += sizeof(WORD); // extended RCODE -- report back to caller INLINE_WRITE_FLIPPED_WORD( pch, pSecPack->ExtendedRcode ); pch += sizeof(WORD); // other length WRITE_UNALIGNED_WORD( pch, 0 ); pch += sizeof(WORD); // set TSIG record datalength Dns_SetRecordDatalength( (PDNS_WIRE_RECORD) ptsigRRHead, (WORD) (pch - ptsigRdataBegin) ); // increment AdditionalCount pMsgHead->AdditionalCount++; DNSDBG( SECURITY, ( "Signed packet at %p with GSS TSIG.\n" "\tsig length = %d\n" "\tTSIG RR header = %p\n" "\tTSIG RDATA = %p\n" "\tTSIG RDATA End = %p\n" "\tTSIG RDATA length = %d\n", pMsgHead, sigLength, ptsigRRHead, ptsigRdataBegin, pch, (WORD) (pch - ptsigRdataBegin) )); *ppCurrent = pch; status = ERROR_SUCCESS; Exit: // free signing buffer // note: no cleanup of allocated pQuerySig is needed; from point // of allocation there is no failure scenario if ( pbufStart ) { FREE_HEAP( pbufStart ); } return( status ); } // // Security record reading // DNS_STATUS Dns_ExtractGssTsigFromMessage( IN OUT PSECPACK pSecPack, IN PDNS_HEADER pMsgHead, IN PCHAR pMsgEnd ) /*++ Routine Description: Extracts a TSIG from packet and loads into security context. Arguments: pSecPack - security info for packet pMsgHead - msg to extract security context from pMsgEnd - end of message Return Value: ERROR_SUCCESS if successful. DNS_ERROR_FORMERR if badly formed TSIG DNS_STATUS_PACKET_UNSECURE if security context in response is same as query's indicating non-security aware partner RCODE or extended RCODE on failure. --*/ { DNS_STATUS status = ERROR_INVALID_DATA; PCHAR pch; PCHAR pnameOwner; WORD nameLength; WORD extRcode; WORD sigLength; DWORD currentTime; PDNS_PARSED_RR pparsedRR; PDNS_RECORD ptsigRR; DNS_RECORD ptempRR; PCHAR psig; DNSDBG( SECURITY, ( "ExtractGssTsigFromMessage( %p )\n", pMsgHead )); // clear any previous TSIG if ( pSecPack->pTsigRR || pSecPack->pszContextName ) // if ( pSecPack->pTsigRR || pSecPack->pszContextName ) { // Dns_RecordFree( pSecPack->pTsigRR ); FREE_HEAP( pSecPack->pTsigRR ); FREE_HEAP( pSecPack->pszContextName ); pSecPack->pTsigRR = NULL; pSecPack->pszContextName = NULL; } // set message pointers pSecPack->pMsgHead = pMsgHead; pSecPack->pMsgEnd = pMsgEnd; // // if no additional record, don't bother, not a secure message // if ( pMsgHead->AdditionalCount == 0 ) { status = DNS_STATUS_PACKET_UNSECURE; goto Failed; } // // skip to security record (last record in packet) // pch = Dns_SkipToRecord( pMsgHead, pMsgEnd, (-1) // goto last record ); if ( !pch ) { status = DNS_ERROR_RCODE_FORMAT_ERROR; goto Failed; } // // read TSIG owner name // pparsedRR = &pSecPack->ParsedRR; pparsedRR->pchName = pch; pch = Dns_ReadPacketNameAllocate( & pSecPack->pszContextName, & nameLength, 0, 0, pch, (PCHAR)pMsgHead, pMsgEnd ); if ( !pch ) { DNSDBG( SECURITY, ( "WARNING: invalid TSIG RR owner name at %p.\n", pch )); status = DNS_ERROR_RCODE_FORMAT_ERROR; goto Failed; } // // parse record structure // pch = Dns_ReadRecordStructureFromPacket( pch, pMsgEnd, pparsedRR ); if ( !pch ) { DNSDBG( SECURITY, ( "ERROR: invalid security RR in packet at %p.\n" "\tstructure or data not withing packet\n", pMsgHead )); status = DNS_ERROR_RCODE_FORMAT_ERROR; goto Failed; } if ( pparsedRR->Type != DNS_TYPE_TSIG ) { status = DNS_STATUS_PACKET_UNSECURE; goto Failed; } if ( pch != pMsgEnd ) { DNSDBG( SECURITY, ( "WARNING: security RR does NOT end at packet end.\n" "\tRR end offset = %04x\n" "\tmsg end offset = %04x\n", pch - (PCHAR)pMsgHead, pMsgEnd - (PCHAR)pMsgHead )); } // // extract TSIG record // // TsigReadRecord() requires RR owner name for versioning // - pass TSIG name in temp RR // ptsigRR = TsigRecordRead( NULL, DnsCharSetWire, NULL, pparsedRR->pchData, pparsedRR->pchNextRR ); if ( !ptsigRR ) { DNSDBG( ANY, ( "ERROR: invalid TSIG RR in packet at %p.\n" "\tstructure or data not withing packet\n", pMsgHead )); status = DNS_ERROR_RCODE_FORMAT_ERROR; DNS_ASSERT( FALSE ); goto Failed; } pSecPack->pTsigRR = ptsigRR; // // currently callers expect error on Extract when ext RCODE is set // if ( ptsigRR->Data.TSIG.wError ) { DNSDBG( SECURITY, ( "Leaving ExtractGssTsig(), TSIG had extended RCODE = %d\n", ptsigRR->Data.TSIG.wError )); status = DNS_ERROR_FROM_RCODE( ptsigRR->Data.TSIG.wError ); goto Failed; } // // Server side: // if query, save off signature for signing response // sigLength = ptsigRR->Data.TSIG.wSigLength; if ( !pMsgHead->IsResponse ) { ASSERT( !pSecPack->pQuerySig ); if ( pSecPack->pQuerySig ) { FREE_HEAP( pSecPack->pQuerySig ); pSecPack->pQuerySig = NULL; } psig = ALLOCATE_HEAP( sigLength ); if ( !psig ) { status = DNS_ERROR_NO_MEMORY; goto Failed; } RtlCopyMemory( psig, ptsigRR->Data.TSIG.pSignature, sigLength ); pSecPack->pQuerySig = psig; pSecPack->QuerySigLength = sigLength; } // // Client side: // check for security record echo on response // // if we signed and got echo signature back, then may have security unaware // server or lost\timed out key condition // else { if ( pSecPack->pQuerySig && pSecPack->QuerySigLength == sigLength && RtlEqualMemory( ptsigRR->Data.TSIG.pSignature, pSecPack->pQuerySig, sigLength ) ) { status = DNS_STATUS_PACKET_UNSECURE; goto Failed; } } status = ERROR_SUCCESS; Failed: if ( status != ERROR_SUCCESS ) { DNS_ASSERT( status != DNS_ERROR_RCODE_FORMAT_ERROR ); ( status == DNS_STATUS_PACKET_UNSECURE ) ? (SecTsigEcho++) : (SecTsigFormerr++); } DNSDBG( SECURITY, ( "Leave ExtractGssTsigFromMessage()\n" "\tpMsgHead = %p\n" "\tsig length = %d\n" "\tpsig = %p\n" "\tOriginalXid = 0x%x\n", "\tpQuerySig = %p\n" "\tQS length = %d\n", pMsgHead, sigLength, ptsigRR->Data.TSIG.pSignature, ptsigRR->Data.TSIG.wOriginalXid, pSecPack->pQuerySig, pSecPack->QuerySigLength )); return( status ); } DNS_STATUS Dns_ExtractGssTkeyFromMessage( IN OUT PSECPACK pSecPack, IN PDNS_HEADER pMsgHead, IN PCHAR pMsgEnd, IN BOOL fIsServer ) /*++ Routine Description: Extracts a TKEY from packet and loads into security context. Arguments: pSecPack - security info for packet pMsgHead - msg to extract security context from pMsgEnd - end of message fIsServer - performing this operation as DNS server? Return Value: ERROR_SUCCESS if successful. DNS_ERROR_FORMERR if badly formed TKEY DNS_STATUS_PACKET_UNSECURE if security context in response is same as query's indicating non-security aware partner RCODE or extended RCODE on failure. --*/ { DNS_STATUS status = ERROR_INVALID_DATA; PCHAR pch; PCHAR pnameOwner; WORD nameLength; DWORD currentTime; PDNS_PARSED_RR pparsedRR; PDNS_RECORD ptkeyRR; WORD returnExtendedRcode = 0; DWORD version; DNSDBG( SECURITY, ( "ExtractGssTkeyFromMessage( %p )\n", pMsgHead )); // // free any previous TKEY // - may have one from previous pass in two pass negotiation // // DCR: name should be attached to TKEY\TSIG record // then lookup made with IP\name pair against context key // no need for pszContextName field // if ( pSecPack->pTkeyRR || pSecPack->pszContextName ) { // Dns_RecordFree( pSecPack->pTkeyRR ); FREE_HEAP( pSecPack->pTkeyRR ); FREE_HEAP( pSecPack->pszContextName ); pSecPack->pTkeyRR = NULL; pSecPack->pszContextName = NULL; } // set message pointers pSecPack->pMsgHead = pMsgHead; pSecPack->pMsgEnd = pMsgEnd; // // skip to TKEY record (second record in packet) // pch = Dns_SkipToRecord( pMsgHead, pMsgEnd, (1) // skip question only ); if ( !pch ) { status = DNS_ERROR_RCODE_FORMAT_ERROR; goto Failed; } // // read TKEY owner name // pparsedRR = &pSecPack->ParsedRR; pparsedRR->pchName = pch; pch = Dns_ReadPacketNameAllocate( & pSecPack->pszContextName, & nameLength, 0, 0, pch, (PCHAR)pMsgHead, pMsgEnd ); if ( !pch ) { DNSDBG( SECURITY, ( "WARNING: invalid TKEY RR owner name at %p.\n", pch )); status = DNS_ERROR_RCODE_FORMAT_ERROR; goto Failed; } // // parse record structure // pch = Dns_ReadRecordStructureFromPacket( pch, pMsgEnd, pparsedRR ); if ( !pch ) { DNSDBG( SECURITY, ( "ERROR: invalid security RR in packet at %p.\n" "\tstructure or data not withing packet\n", pMsgHead )); status = DNS_ERROR_RCODE_FORMAT_ERROR; goto Failed; } if ( pparsedRR->Type != DNS_TYPE_TKEY ) { status = DNS_ERROR_RCODE_FORMAT_ERROR; DNS_ASSERT( status != DNS_ERROR_RCODE_FORMAT_ERROR ); goto Failed; } if ( pch != pMsgEnd && pMsgHead->AdditionalCount == 0 ) { DNSDBG( SECURITY, ( "WARNING: TKEY RR does NOT end at packet end and no TSIG is present.\n" "\tRR end offset = %04x\n" "\tmsg end offset = %04x\n", pch - (PCHAR)pMsgHead, pMsgEnd - (PCHAR)pMsgHead )); } // // extract TKEY record // ptkeyRR = TkeyRecordRead( NULL, DnsCharSetWire, NULL, // message buffer unknown pparsedRR->pchData, pparsedRR->pchNextRR ); if ( !ptkeyRR ) { DNSDBG( ANY, ( "ERROR: invalid TKEY RR data in packet at %p.\n", pMsgHead )); status = DNS_ERROR_RCODE_FORMAT_ERROR; goto Failed; } pSecPack->pTkeyRR = ptkeyRR; // // verify GSS algorithm and mode name // // if server, save off version for later responses // if ( RtlEqualMemory( ptkeyRR->Data.TKEY.pAlgorithmPacket, g_pAlgorithmNameCurrent, GSS_ALGORITHM_NAME_PACKET_LENGTH ) ) { version = TKEY_VERSION_CURRENT; } else if ( RtlEqualMemory( ptkeyRR->Data.TKEY.pAlgorithmPacket, g_pAlgorithmNameW2K, W2K_GSS_ALGORITHM_NAME_PACKET_LENGTH ) ) { version = TKEY_VERSION_W2K; } else { DNSDBG( ANY, ( "ERROR: TKEY record is NOT GSS alogrithm.\n" )); returnExtendedRcode = DNS_RCODE_BADKEY; goto Failed; } // save client version // need additional check on TKEY_VERSION_CURRENT as Whistler // beta clients had fixed AlgorithmName but were still not // generating unique keys, so need separate version to handle them if ( fIsServer ) { if ( version == TKEY_VERSION_CURRENT ) { version = Dns_GetKeyVersion( pSecPack->pszContextName ); if ( version == 0 ) { // note, this essentially means unknown non-MS client DNSDBG( SECURITY, ( "Non-MS TKEY client.\n" "\tkey name = %s\n", pSecPack->pszContextName )); version = TKEY_VERSION_CURRENT; } } pSecPack->TkeyVersion = version; } // mode if ( ptkeyRR->Data.TKEY.wMode != DNS_TKEY_MODE_GSS ) { DNSDBG( SECURITY, ( "ERROR: non-GSS mode (%d) in TKEY\n", ptkeyRR->Data.TKEY.wMode )); returnExtendedRcode = DNS_RCODE_BADKEY; goto Failed; } // // allow small time slew, otherwise must have fresh key // currentTime = (DWORD) time(NULL); if ( ptkeyRR->Data.TKEY.dwCreateTime > ptkeyRR->Data.TKEY.dwExpireTime || ptkeyRR->Data.TKEY.dwExpireTime + MAX_TIME_SKEW < currentTime ) { DNSDBG( ANY, ( "ERROR: TKEY failed expire time check.\n" "\tcreate time = %d\n" "\texpire time = %d\n" "\tcurrent time = %d\n", ptkeyRR->Data.TKEY.dwCreateTime, ptkeyRR->Data.TKEY.dwExpireTime, currentTime )); if ( !SecBigTimeSkew || ptkeyRR->Data.TKEY.dwExpireTime + SecBigTimeSkew < currentTime ) { returnExtendedRcode = DNS_RCODE_BADTIME; SecTkeyBadTime++; goto Failed; } DNSDBG( ANY, ( "REPRIEVED: TKEY Time slew %d withing %d allowable slew!\n", currentTime - ptkeyRR->Data.TKEY.dwCreateTime, SecBigTimeSkew )); SecBigTimeSkewBypass++; } // // currently callers expect error on Extract when ext RCODE is set // if ( ptkeyRR->Data.TKEY.wError ) { DNSDBG( SECURITY, ( "Leaving ExtractGssTkey(), TKEY had extended RCODE = %d\n", ptkeyRR->Data.TKEY.wError )); status = DNS_ERROR_FROM_RCODE( ptkeyRR->Data.TKEY.wError ); goto Failed; } #if 0 // // check for security record echo on response // // if we get echo of TKEY back, then probably simple, no-secure server // #endif // // pack key token into GSS security token buffer // do this here simply to avoid doing in both client and server routines // pSecPack->RemoteBuf.pvBuffer = ptkeyRR->Data.TKEY.pKey; pSecPack->RemoteBuf.cbBuffer = ptkeyRR->Data.TKEY.wKeyLength; pSecPack->RemoteBuf.BufferType = SECBUFFER_TOKEN; status = ERROR_SUCCESS; Failed: if ( status != ERROR_SUCCESS ) { SecTkeyInvalid++; } // if failed with extended RCODE, set for return if ( returnExtendedRcode ) { pSecPack->ExtendedRcode = returnExtendedRcode; status = DNS_ERROR_FROM_RCODE( returnExtendedRcode ); } DNSDBG( SECURITY, ( "Leave ExtractGssTkeyFromMessage()\n" "\tstatus = %08x (%d)\n" "\tpMsgHead = %p\n" "\tpkey = %p\n" "\tlength = %d\n", status, status, pMsgHead, pSecPack->RemoteBuf.pvBuffer, pSecPack->RemoteBuf.cbBuffer )); return( status ); } PCHAR Dns_CopyAndCanonicalizeWireName( IN PCHAR pszInput, OUT PCHAR pszOutput, OUT DWORD dwOutputSize ) /*++ Routine Description: Copy a UTF-8 uncompressed DNS wire packet name performing canonicalization during the copy. Arguments: pszInput -- pointer to input buffer pszOutput -- pointer to output buffer dwOutputSize -- number of bytes available at output buffer Return Value: Returns a pointer to the byte after the last byte written into the output buffer or NULL on error. --*/ { UCHAR labelLength; WCHAR wszlabel[ DNS_MAX_LABEL_BUFFER_LENGTH + 1 ]; DWORD bufLength; DWORD outputCharsRemaining = dwOutputSize; DWORD dwtemp; PCHAR pchlabelLength; while ( ( labelLength = *pszInput++ ) != 0 ) { // // Error if this label is too long or if the output buffer can't // hold at least as many chars as in the uncanonicalized buffer. // if ( labelLength > DNS_MAX_LABEL_LENGTH || outputCharsRemaining < labelLength ) { goto Error; } // // Copy this UTF-8 label to a Unicode buffer. // bufLength = DNS_MAX_NAME_BUFFER_LENGTH_UNICODE; if ( !Dns_NameCopy( ( PCHAR ) wszlabel, &bufLength, pszInput, labelLength, DnsCharSetUtf8, DnsCharSetUnicode ) ) { goto Error; } pszInput += labelLength; // // Canonicalize the buffer. // dwtemp = Dns_MakeCanonicalNameInPlaceW( wszlabel, ( DWORD ) labelLength ); if ( dwtemp == 0 || dwtemp > DNS_MAX_LABEL_LENGTH ) { goto Error; } labelLength = ( UCHAR ) dwtemp; // // Copy the label to the output buffer. // pchlabelLength = pszOutput++; // Reserve byte for label length. dwtemp = outputCharsRemaining; if ( !Dns_NameCopy( pszOutput, &dwtemp, ( PCHAR ) wszlabel, labelLength, DnsCharSetUnicode, DnsCharSetUtf8 ) ) { goto Error; } outputCharsRemaining -= dwtemp; --dwtemp; // Don't include NULL in label length. *pchlabelLength = ( UCHAR ) dwtemp; pszOutput += dwtemp; } // // Add name terminator. // *pszOutput++ = 0; return pszOutput; Error: return NULL; } // Dns_CopyAndCanonicalizeWireName DNS_STATUS Dns_VerifySignatureOnPacket( IN PSECPACK pSecPack ) /*++ Routine Description: Verify signature on packet contained in security record. Arguments: pSecPack - security packet session info Return Value: ERROR_SUCCESS on success DNS_ERROR_BADSIG if sig doesn't exist or doesn't verify DNS_ERROR_BADTIME if sig expired Extended RCODE from caller if set. --*/ { PSEC_CNTXT psecCtxt; PDNS_HEADER pmsgHead = pSecPack->pMsgHead; PCHAR pmsgEnd = pSecPack->pMsgEnd; PDNS_RECORD ptsigRR; PDNS_PARSED_RR pparsedRR; DWORD currentTime; PCHAR pbufStart = NULL; PCHAR pbuf; DNS_STATUS status; DWORD length; WORD returnExtendedRcode = 0; SecBufferDesc bufferDesc; SecBuffer buffer[2]; WORD msgXid; DWORD version; BOOL fcanonicalizeTsigOwnerName; DNSDBG( SECURITY, ( "VerifySignatureOnPacket( %p )\n", pmsgHead )); // // get security context // psecCtxt = pSecPack->pSecContext; if ( !psecCtxt ) { DNS_PRINT(( "ERROR: attempted signing without security context!!!\n" )); ASSERT( FALSE ); return( DNS_ERROR_RCODE_BADKEY ); } // // if no signature extracted from packet, we're dead // pparsedRR = &pSecPack->ParsedRR; ptsigRR = pSecPack->pTsigRR; if ( !ptsigRR ) { returnExtendedRcode = DNS_RCODE_BADSIG; goto Exit; } // // validity check GSS-TSIG // - GSS algorithm // - valid time // - extract extended RCODE // // DCR_ENHANCE: check tampering on bad TSIG? // - for tampered algorithm all we can do is immediate return // - but can check signature and detect tampering // before excluding or basis or time or believing ext RCODE // // check algorithm name if ( RtlEqualMemory( ptsigRR->Data.TKEY.pAlgorithmPacket, g_pAlgorithmNameCurrent, GSS_ALGORITHM_NAME_PACKET_LENGTH ) ) { version = TKEY_VERSION_CURRENT; } else if ( RtlEqualMemory( ptsigRR->Data.TKEY.pAlgorithmPacket, g_pAlgorithmNameW2K, W2K_GSS_ALGORITHM_NAME_PACKET_LENGTH ) ) { version = TKEY_VERSION_W2K; } else { DNSDBG( ANY, ( "ERROR: TSIG record is NOT GSS alogrithm.\n" )); returnExtendedRcode = DNS_RCODE_BADSIG; goto Exit; } // // set version if server // - if don't know our version, must be server // note: alternative is fIsServer flag or IsServer to SecPack // if ( psecCtxt->Version == 0 ) { psecCtxt->Version = version; } // // time check // - should be within specified fudge of signing time // currentTime = (DWORD) time(NULL); if ( (LONGLONG)currentTime > ptsigRR->Data.TSIG.i64CreateTime + (LONGLONG)ptsigRR->Data.TSIG.wFudgeTime || (LONGLONG)currentTime < ptsigRR->Data.TSIG.i64CreateTime - (LONGLONG)ptsigRR->Data.TSIG.wFudgeTime ) { DNSDBG( ANY, ( "ERROR: TSIG failed fudge time check.\n" "\tcreate time = %I64d\n" "\tfudge time = %d\n" "\tcurrent time = %d\n", ptsigRR->Data.TSIG.i64CreateTime, ptsigRR->Data.TSIG.wFudgeTime, currentTime )); // // DCR_FIX: currently not enforcing time check // in fact have ripped out the counter to track failures // within some allowed skew } // // extended RCODE -- follows signature // - if set, report back to caller // if ( ptsigRR->Data.TSIG.wError ) { DNSDBG( SECURITY, ( "Leaving ExtractGssTsig(), TSIG had extended RCODE = %d\n", ptsigRR->Data.TSIG.wError )); status = DNS_ERROR_FROM_RCODE( ptsigRR->Data.TSIG.wError ); goto Exit; } // // create signing buffer // - everything signed must fit into message // pbuf = ALLOCATE_HEAP( MAX_SIGNING_SIZE ); if ( !pbuf ) { status = DNS_ERROR_NO_MEMORY; goto Exit; } pbufStart = pbuf; // // verify signature over: // - query signature (if exists) // - message // - without TSIG in Additional count // - with original XID // - TSIG owner name // - TSIG header // - class // - TTL // - TSIG RDATA // - everything before SigLength // - other data length and other data // if ( pmsgHead->IsResponse ) { if ( pSecPack->pQuerySig ) { WORD sigLength = pSecPack->QuerySigLength; ASSERT( sigLength ); DNS_ASSERT( psecCtxt->Version != 0 ); if ( psecCtxt->Version >= TKEY_VERSION_XP_RC1 ) { DNSDBG( SECURITY, ( "New verify sig including query sig length =%x\n", sigLength )); INLINE_WRITE_FLIPPED_WORD( pbuf, sigLength ); pbuf += sizeof(WORD); } RtlCopyMemory( pbuf, pSecPack->pQuerySig, sigLength ); pbuf += sigLength; } // if server has just completed TKEY nego, it may sign response without query // so client need not have query sig // in all other cases client must have query sig to verify response else if ( !pSecPack->pTkeyRR ) { DNS_PRINT(( "ERROR: verify on response at %p without having QUERY signature!\n", pmsgHead )); ASSERT( FALSE ); returnExtendedRcode = DNS_RCODE_BADSIG; goto Exit; } DNSDBG( SECURITY, ( "Verifying TSIG on TKEY response without query sig.\n" )); } // // copy message // - go right through, TSIG owner name // - message header MUST be in network order // - does NOT include TSIG record in additional count // - must have orginal XID in place // (save existing XID and replace with orginal, then // restore after copy) // ASSERT( pmsgHead->AdditionalCount ); pmsgHead->AdditionalCount--; msgXid = pmsgHead->Xid; DNS_BYTE_FLIP_HEADER_COUNTS( pmsgHead ); // // If need to canonicalize the TSIG owner name, copy to the start // of the name; else copy to the end of the name. // fcanonicalizeTsigOwnerName = !psecCtxt->fClient && psecCtxt->Version >= TKEY_VERSION_CURRENT; length = ( DWORD ) ( ( fcanonicalizeTsigOwnerName ? pparsedRR->pchName : pparsedRR->pchRR ) - ( PCHAR ) pmsgHead ); // restore original XID pmsgHead->Xid = ptsigRR->Data.TSIG.wOriginalXid; RtlCopyMemory( pbuf, (PCHAR) pmsgHead, length ); pbuf += length; DNS_BYTE_FLIP_HEADER_COUNTS( pmsgHead ); pmsgHead->AdditionalCount++; pmsgHead->Xid = msgXid; // // If the TSIG owner name needs to be canonicalized, write it out // to the signing buffer in canonical form (lower case). // if ( fcanonicalizeTsigOwnerName ) { pbuf = Dns_CopyAndCanonicalizeWireName( pparsedRR->pchName, pbuf, MAXDWORD ); if ( pbuf == NULL ) { DNSDBG( SECURITY, ( "Unable to canonicalize TSIG owner name at %p", pparsedRR->pchName )); returnExtendedRcode = DNS_RCODE_BADSIG; goto Exit; } } // copy TSIG class and TTL // - currently always zero INLINE_WRITE_FLIPPED_WORD( pbuf, pparsedRR->Class ); pbuf += sizeof(WORD); INLINE_WRITE_FLIPPED_DWORD( pbuf, pparsedRR->Ttl ); pbuf += sizeof(DWORD); // copy TSIG RDATA up to signature length length = (DWORD)(ptsigRR->Data.TSIG.pSignature - sizeof(WORD) - pparsedRR->pchData); ASSERT( (INT)length < (pparsedRR->DataLength - ptsigRR->Data.TSIG.wSigLength) ); RtlCopyMemory( pbuf, pparsedRR->pchData, length ); pbuf += length; // copy extended RCODE -- report back to caller INLINE_WRITE_FLIPPED_WORD( pbuf, ptsigRR->Data.TSIG.wError ); pbuf += sizeof(WORD); // copy other data length and other data // - currently just zero length field INLINE_WRITE_FLIPPED_WORD( pbuf, ptsigRR->Data.TSIG.wOtherLength ); pbuf += sizeof(WORD); length = ptsigRR->Data.TSIG.wOtherLength; if ( length ) { RtlCopyMemory( pbuf, ptsigRR->Data.TSIG.pOtherData, length ); pbuf += length; } // calculate total length signature is over length = (DWORD)(pbuf - pbufStart); // // verify signature // buf[0] is data // buf[1] is signature // // signature is verified directly in packet buffer // bufferDesc.ulVersion = 0; bufferDesc.cBuffers = 2; bufferDesc.pBuffers = buffer; // signature is over everything up to signature itself buffer[0].pvBuffer = pbufStart; buffer[0].cbBuffer = length; buffer[0].BufferType = SECBUFFER_DATA; // sig MUST be pointed to by remote buffer // // DCR: can pull copy when eliminate retry below // // copy packet signature as signing is destructive // and want to allow for retry // buffer[1].pvBuffer = ptsigRR->Data.TSIG.pSignature; buffer[1].cbBuffer = ptsigRR->Data.TSIG.wSigLength; buffer[1].BufferType = SECBUFFER_TOKEN; IF_DNSDBG( SECURITY ) { DnsPrint_Lock(); DNS_PRINT(( "Doing VerifySignature() on packet %p.\n" "\tpSecPack = %p\n" "\tpSecCntxt = %p\n", pmsgHead, pSecPack, psecCtxt )); DNS_PRINT(( "Verify sig info:\n" "\tsign data buf %p\n" "\t length %d\n" "\tsignature buf %p (in packet)\n" "\t length %d\n", buffer[0].pvBuffer, buffer[0].cbBuffer, buffer[1].pvBuffer, buffer[1].cbBuffer )); DnsDbg_RawOctets( "Signing buffer:", NULL, buffer[0].pvBuffer, buffer[0].cbBuffer ); DnsDbg_RawOctets( "Signature:", NULL, buffer[1].pvBuffer, buffer[1].cbBuffer ); DnsDbg_SecurityContext( "Verify context", psecCtxt ); DnsPrint_Unlock(); } status = g_pSecurityFunctionTable->VerifySignature( & psecCtxt->hSecHandle, & bufferDesc, 0, NULL ); if ( status != SEC_E_OK ) { IF_DNSDBG( SECURITY ) { DnsPrint_Lock(); DNS_PRINT(( "ERROR: TSIG does not match on packet %p.\n" "\tVerifySignature() status = %d (%08x)\n" "\tpSecPack = %p\n" "\tpSecCntxt = %p\n" "\thSecHandle = %p\n", pmsgHead, status, status, pSecPack, psecCtxt, & psecCtxt->hSecHandle )); DNS_PRINT(( "Verify sig info:\n" "\tsign data buf %p\n" "\t length %d\n" "\tsignature buf %p (in packet)\n" "\t length %d\n", buffer[0].pvBuffer, buffer[0].cbBuffer, buffer[1].pvBuffer, buffer[1].cbBuffer )); DnsDbg_RawOctets( "Signing buffer:", NULL, buffer[0].pvBuffer, buffer[0].cbBuffer ); DnsDbg_RawOctets( "Signature:", NULL, buffer[1].pvBuffer, buffer[1].cbBuffer ); DnsDbg_SecurityContext( "Verify failed context", psecCtxt ); DnsDbg_MessageNoContext( "Message TSIG verify failed on:", pmsgHead, 0 ); DnsPrint_Unlock(); } SecTsigVerifyFailed++; returnExtendedRcode = DNS_RCODE_BADSIG; goto Exit; } SecTsigVerifySuccess++; Exit: // free signing data buffer FREE_HEAP( pbufStart ); // if failed with extended RCODE, set for return if ( returnExtendedRcode ) { pSecPack->ExtendedRcode = returnExtendedRcode; status = DNS_ERROR_FROM_RCODE( returnExtendedRcode ); } DNSDBG( SECURITY, ( "Leave VerifySignatureOnPacket( %p )\n" "\tstatus %d (%08x)\n" "\text RCODE %d\n", pmsgHead, status, status, pSecPack->ExtendedRcode )); return( status ); } // // Client session routines // DNS_STATUS Dns_NegotiateTkeyWithServer( OUT PHANDLE phContext, IN DWORD dwFlag, IN LPSTR pszNameServer, IN PIP_ARRAY aipServer, IN PCHAR pCreds, OPTIONAL IN PCHAR pszContext, OPTIONAL IN DWORD Version ) /*++ Routine Description: Negotiate TKEY with a DNS server. Arguments: phContext -- addr to recv context (SEC_CNTXT) negotiated dwFlags -- flags pszNameServer -- server to update apiServer -- server to update pCreds -- credentials; if not given use default process creds pszContext -- security context name; name for unique negotiated security session between client and server; if not given create made up server\pid name for context Version -- verion Return Value: ERROR_SUCCESS if successful. Error status on failure. --*/ { DNS_STATUS status; PSEC_CNTXT psecCtxt = NULL; SECPACK secPack; PCHAR pch; PWSTR pcredKey = NULL; DNS_SECCTXT_KEY key; DWORD i; BOOL fdoneNegotiate = FALSE; PDNS_MSG_BUF pmsgSend = NULL; PDNS_MSG_BUF pmsgRecv = NULL; WORD length; IP_ADDRESS serverIp = aipServer->AddrArray[0]; CHAR defaultContextBuffer[64]; BOOL fserverW2K = FALSE; DWORD recvCount; PCHAR pcurrentAfterQuestion; DNSDBG( SECURITY, ( "Enter Dns_NegotiateTkeyWithServer()\n" "\tflags = %08x\n" "\tserver IP = %s\n" "\tserver name = %s\n" "\tpCreds = %p\n" "\tcontext = %s\n", dwFlag, IP_STRING( serverIp ), pszNameServer, pCreds, pszContext )); DNS_ASSERT( pszNameServer ); // it better be there! // init first so all error paths are safe Dns_InitSecurityPacketInfo( &secPack, NULL ); // start security status = Dns_StartSecurity( FALSE ); if ( status != ERROR_SUCCESS ) { goto Cleanup; } // // build key // RtlZeroMemory( &key, sizeof(key) ); // // if have creds, create a "cred key" to uniquely identify // if ( pCreds ) { pcredKey = MakeCredKey( pCreds ); if ( !pcredKey ) { DNSDBG( ANY, ( "Failed cred key alloc -- failing nego!\n" )); status = DNS_ERROR_NO_MEMORY; goto Cleanup; } key.pwsCredKey = pcredKey; } // // context name // - if no context name, concatentate // - process ID // - current user's domain-relative ID // this makes ID unique to process\security context // (IP handles issue of different machines) // // versioning note: // - it is NOT necessary to version using the KEY name // - the point is to allow us to easily interoperate with previous // client versions which may have bugs relative to the final spec // // versions so far // - W2K beta2 (-02) included XID // - W2K (-03) sent TKEY in answer and used "gss.microsoft.com" // as algorithm name // - SP1(or2) and whistler beta2 (-MS-04) used "gss-tsig" // - XP post beta 2 (-MS-05) generates unique context name to // avoid client collisions // - XP RC1 (-MS-06) RFC compliant signing with query sig length included // - XP RC2+ canonicalization of TSIG name in signing buffer // // server version use: // - the Win2K server does detect version 02 and fixup the XID // signing to match client // - current (whistler) server does NOT use the version field // // however to enable server to detect whistler beta2 client -- // just in case there's another problem relative to the spec -- // i'm maintaining field; // however note that the field will be 04, even if the client // realizes it is talking to a W2K server and falls back to W2K // client behavior; in other words NEW server will see 04, but // W2K server only knows it is NOT talking to 02 server which is // all it cares about; // // key idea: this can be used to detect a particular MS client // when there's a behavior question ... but it is NOT a spec'd // versioning mechanism and other clients will come in with // no version tag and must be treated per spec // // Key string selection: it is important that the key string be // in "canonical" form as per RFC 2535 section 8.1 - basically this // means lower case. Since the key string is canonical it doesn't // matter if the server does or doesn't canonicalize the string // when building the signing buffer. // if ( Version == 0 ) { Version = TKEY_VERSION_CURRENT; } if ( !pszContext ) { sprintf( defaultContextBuffer, "%d-ms-%d", //Dns_GetCurrentRid(), GetCurrentProcessId(), Version ); pszContext = defaultContextBuffer; DNSDBG( SECURITY, ( "Generated secure update key context %s\n", pszContext )); } key.pszClientContext = pszContext; // // check for negotiated security context // - check for context to any of server IPs // - dump, if partially negotiated or forcing renegotiated // for( i=0; iAddrCount; i++ ) { key.IpRemote = aipServer->AddrArray[i]; psecCtxt = Dns_DequeueSecurityContextByKey( key, TRUE ); if ( psecCtxt ) { if ( !psecCtxt->fNegoComplete || (dwFlag & DNS_UPDATE_FORCE_SECURITY_NEGO) ) { DNSDBG( ANY, ( "Warning: Deleting context to negotiate a new one.\n" "\tKey: [%s, %s]\n" "\tReason: %s\n", IP_STRING( key.IpRemote ), key.pszTkeyName, psecCtxt->fNegoComplete ? "User specified FORCE_SECURITY_NEGO flag." : "Incomplete negotiation key exists." )); Dns_FreeSecurityContext( psecCtxt ); } else // have valid context -- we're done! { ASSERT( psecCtxt->fNegoComplete ); DNSDBG( SECURITY, ( "Returning existing negotiated context at %p\n", psecCtxt )); goto Cleanup; } } } // // create new context and security packet info // - use first server IP in key // key.IpRemote = serverIp; psecCtxt = Dns_FindOrCreateSecurityContext( key ); if ( !psecCtxt ) { status = DNS_RCODE_SERVER_FAILURE; goto Cleanup; } secPack.pSecContext = psecCtxt; psecCtxt->Version = Version; // // have creds -- get cred handle // if ( pCreds ) { status = Dns_AcquireCredHandle( &psecCtxt->CredHandle, FALSE, // client pCreds ); if ( status != ERROR_SUCCESS ) { DNSDBG( SECURITY, ( "Failed AcquireCredHandle -- failing nego!\n" )); goto Cleanup; } psecCtxt->fHaveCredHandle = TRUE; } // allocate message buffers length = DNS_TCP_DEFAULT_ALLOC_LENGTH; pmsgSend= Dns_AllocateMsgBuf( length ); if ( !pmsgSend) { DNS_PRINT(( "ERROR: failed allocation.\n" )); status = GetLastError(); goto Cleanup; } pmsgRecv = Dns_AllocateMsgBuf( length ); if ( !pmsgRecv ) { DNS_PRINT(( "ERROR: failed allocation.\n")); status = GetLastError(); goto Cleanup; } // init remote sockaddr and socket // setup receive buffer for TCP DnsInitializeMsgRemoteSockaddr( pmsgSend, serverIp ); pmsgSend->Socket = 0; pmsgSend->fTcp = TRUE; SET_MESSAGE_FOR_TCP_RECV( pmsgRecv ); pmsgRecv->Timeout = SECURE_UPDATE_TCP_TIMEOUT; // // build packet // - query opcode // - leave non-recursive (so downlevel server doesn't recurse query) // - write TKEY question // - write TKEY itself // pch = Dns_WriteQuestionToMessage( pmsgSend, psecCtxt->Key.pszTkeyName, DNS_TYPE_TKEY, FALSE // not unicode ); if ( !pch ) { status = ERROR_INVALID_PARAMETER; goto Cleanup; } pcurrentAfterQuestion = pch; pmsgSend->MessageHead.RecursionDesired = 0; pmsgSend->MessageHead.Opcode = DNS_OPCODE_QUERY; // // init XID to something fairly random // pmsgSend->MessageHead.Xid = Dns_GetRandomXid( pmsgSend ); // // for given server send in a loop // - write TKEY context to packet // - send \ recv // - may have multiple sends until negotiate a TKEY // while ( 1 ) { // setup session context // on first pass this just builds our context, // on second pass we munge in servers response status = Dns_InitClientSecurityContext( &secPack, pszNameServer, & fdoneNegotiate ); // always recover context pointer, as bad context may be deleted psecCtxt = secPack.pSecContext; ASSERT( psecCtxt || (status != ERROR_SUCCESS && status != DNS_STATUS_CONTINUE_NEEDED) ); if ( status == ERROR_SUCCESS ) { DNSDBG( SECURITY, ( "Successfully negotiated TKEY.\n" )); ASSERT( psecCtxt->fNegoComplete ); // // if completed and remote packet had SIG -- verify SIG // status = Dns_ExtractGssTsigFromMessage( &secPack, & pmsgRecv->MessageHead, DNS_MESSAGE_END( pmsgRecv ) ); if ( status == ERROR_SUCCESS ) { status = Dns_VerifySignatureOnPacket( &secPack ); if ( status != ERROR_SUCCESS ) { DNSDBG( SECURITY, ( "Verify signature failed on TKEY nego packet %p.\n" "\tserver = %s\n" "\tstatus = %d (%08x)\n" "\treturning BADSIG\n", pmsgRecv, IP_STRING( serverIp ), status, status )); status = DNS_ERROR_RCODE_BADSIG; } } else if ( status == DNS_STATUS_PACKET_UNSECURE ) { DNSDBG( SECURITY, ( "WARNING: Unsigned final TKEY nego response packet %p.\n" "\tfrom server %s\n", pmsgRecv, IP_STRING( serverIp ) )); status = ERROR_SUCCESS; } // nego is done, break out of nego loop // any other error on TSIG, falls through as failure break; } // // if not complete, then anything other than continue is failure // else if ( status != DNS_STATUS_CONTINUE_NEEDED ) { goto Cleanup; } // // loop for sign and send // // note this is only in a loop to enable backward compatibility // with "TKEY-in-answer" bug in Win2000 DNS server // recvCount = 0; while ( 1 ) { // // backward compatibility with Win2000 TKEY // - set version to write like W2K // - reset packet to just-wrote-question state // if ( fserverW2K && recvCount == 0 ) { psecCtxt->Version = TKEY_VERSION_W2K; pmsgSend->pCurrent = pcurrentAfterQuestion; pmsgSend->MessageHead.AdditionalCount = 0; pmsgSend->MessageHead.AnswerCount = 0; Dns_CloseConnection( pmsgSend->Socket ); pmsgSend->Socket = 0; } // // write security record with context into packet // // note: fNeedTkeyInAnswer determines whether write // to Answer or Additional section status = Dns_WriteGssTkeyToMessage( (HANDLE) &secPack, & pmsgSend->MessageHead, pmsgSend->pBufferEnd, & pmsgSend->pCurrent, FALSE // client ); if ( status != ERROR_SUCCESS ) { goto Cleanup; } // // if finished negotiation -- sign // if ( fdoneNegotiate ) { DNSDBG( SECURITY, ( "Signing TKEY packet at %p, after successful nego.\n", pmsgSend )); status = Dns_SignMessageWithGssTsig( & secPack, & pmsgSend->MessageHead, pmsgSend->pBufferEnd, & pmsgSend->pCurrent ); if ( status != ERROR_SUCCESS ) { DNSDBG( SECURITY, ( "ERROR: Failed signing TKEY packet at %p, after successful nego.\n" "\tsending without TSIG ...\n", pmsgSend )); } } // // if already connected, send // if first pass, try server IPs, until find one to can connect to // if ( pmsgSend->Socket ) { status = DnsSend( pmsgSend ); } else { for( i=0; iAddrCount; i++ ) { serverIp = aipServer->AddrArray[i]; status = Dns_OpenTcpConnectionAndSend( pmsgSend, serverIp, TRUE ); if ( status != ERROR_SUCCESS ) { if ( pmsgSend->Socket ) { Dns_CloseSocket( pmsgSend->Socket ); pmsgSend->Socket = 0; } continue; } psecCtxt->Key.IpRemote = serverIp; break; } } if ( status != ERROR_SUCCESS ) { goto Done; } // // receive response // - if successful receive, done // - if timeout continue // - other errors indicate some setup or system level // problem // pmsgRecv->Socket = pmsgSend->Socket; status = Dns_RecvTcp( pmsgRecv ); if ( status != ERROR_SUCCESS ) { // W2K server may "eat" bad TKEY packet // if just got a connection, and then timed out, good // chance the problem is W2K server if ( status == ERROR_TIMEOUT && recvCount == 0 && !fserverW2K ) { DNS_PRINT(( "Timeout on TKEY nego -- retry with W2K protocol.\n" )); fserverW2K = TRUE; recvCount = 0; continue; } // indicate error only with this server by setting RCODE pmsgRecv->MessageHead.ResponseCode = DNS_RCODE_SERVER_FAILURE; goto Done; } recvCount++; // // verify XID match // if ( pmsgRecv->MessageHead.Xid != pmsgSend->MessageHead.Xid ) { DNS_PRINT(( "ERROR: Incorrect XID in response. Ignoring.\n" )); goto Done; } // // RCODE failure // // special case Win2K gold DNS server accepting only TKEY // in Answer section // - rcode FORMERR // - haven't already switched to Additional (prevent looping) // if ( pmsgRecv->MessageHead.ResponseCode != DNS_RCODE_NO_ERROR ) { if ( pmsgRecv->MessageHead.ResponseCode == DNS_RCODE_FORMERR && ! fserverW2K && recvCount == 1 ) { DNS_PRINT(( "Formerr TKEY nego -- retry with W2K protocol.\n" )); fserverW2K = TRUE; recvCount = 0; continue; } // done with this server, may be able to continue with others // depending on RCODE goto Done; } // successful send\recv break; } // // not yet finished negotiation // use servers security context to reply to server // if server replied with original context then it is unsecure // => we're done // status = Dns_ExtractGssTkeyFromMessage( (HANDLE) &secPack, &pmsgRecv->MessageHead, DNS_MESSAGE_END( pmsgRecv ), FALSE // fIsServer ); if ( status != ERROR_SUCCESS ) { if ( status == DNS_STATUS_PACKET_UNSECURE ) { DNSDBG( SECURITY, ( "Unsecure update response from server %s.\n" "\tupdate considered successful, quiting.\n", IP_STRING( aipServer->AddrArray[i] ) )); status = ERROR_SUCCESS; ASSERT( FALSE ); goto Cleanup; } break; } } Done: // // check response code // - consider some response codes // switch( status ) { case ERROR_SUCCESS: status = Dns_MapRcodeToStatus( pmsgRecv->MessageHead.ResponseCode ); break; case ERROR_TIMEOUT: DNS_PRINT(( "ERROR: connected to server at %s\n" "\tbut no response to packet at %p\n", MSG_REMOTE_IP_STRING( pmsgSend ), pmsgSend )); break; default: DNS_PRINT(( "ERROR: connected to server at %s to send packet %p\n" "\tbut error %d (%08x) encountered on receive.\n", MSG_REMOTE_IP_STRING( pmsgSend ), pmsgSend, status, status )); break; } Cleanup: DNSDBG( SECURITY, ( "Leaving Dns_NegotiateTkeyWithServer() status = %08x (%d)\n", status, status )); // // if successful return context handle // if not returned or cached, clean up // if ( status == ERROR_SUCCESS ) { if ( phContext ) { *phContext = (HANDLE) psecCtxt; psecCtxt = NULL; } else if ( dwFlag & DNS_UPDATE_CACHE_SECURITY_CONTEXT ) { Dns_EnlistSecurityContext( psecCtxt ); psecCtxt = NULL; } } if ( psecCtxt ) { Dns_FreeSecurityContext( psecCtxt ); } // cleanup session info Dns_CleanupSecurityPacketInfoEx( &secPack, FALSE ); // close connection if ( pmsgSend && pmsgSend->Socket ) { Dns_CloseConnection( pmsgSend->Socket ); } // // DCR_CLEANUP: what's the correct screening here for error codes? // possibly should take all security errors to // status = DNS_ERROR_RCODE_BADKEY; // or some to some status that means unsecure server // and leave BADKEY for actual negotiations that yield bad token // FREE_HEAP( pmsgRecv ); FREE_HEAP( pmsgSend ); FREE_HEAP( pcredKey ); return( status ); } DNS_STATUS Dns_DoSecureUpdate( IN PDNS_MSG_BUF pMsgSend, OUT PDNS_MSG_BUF pMsgRecv, IN OUT PHANDLE phContext, IN DWORD dwFlag, IN PDNS_NETINFO pNetworkInfo, IN PIP_ARRAY aipServer, IN LPSTR pszNameServer, IN PCHAR pCreds, OPTIONAL IN PCHAR pszContext OPTIONAL ) /*++ Routine Description: Main client routine to do secure update. Arguments: pMsgSend - message to send ppMsgRecv - and reuse aipServer -- IP array DNS servers pNetworkInfo -- network info blob for update pszNameServer -- name server name pCreds -- credentials; if not given use default process creds pszContext -- name for security context; this is unique name for session between this process and this server with these creds Return Value: ERROR_SUCCESS if successful. Error status on failure. --*/ { #define FORCE_VERSION_OLD ("Force*Version*Old") DNS_STATUS status = ERROR_SUCCESS; PSEC_CNTXT psecCtxt = NULL; DWORD i; INT retry; IP_ADDRESS serverIp = aipServer->AddrArray[0]; SECPACK secPack; #if 0 DWORD version; #endif DNS_ASSERT( pMsgSend->MessageHead.Opcode == DNS_OPCODE_UPDATE ); DNS_ASSERT( serverIp && pszNameServer ); // it better be there! DNSDBG( SEND, ( "Enter Dns_DoSecureUpdate()\n" "\tsend msg at %p\n" "\tsec context %p\n" "\tserver name %s\n" "\tserver IP %s\n" "\tpCreds %p\n" "\tcontext %s\n", pMsgSend, phContext ? *phContext : NULL, pszNameServer, IP_STRING( serverIp ), pCreds, pszContext )); // // version setting // // note: to set different version we'd need some sort of tag // like pszContext (see example) // but a better way to do this would be tail recursion in just // NegotiateTkey -- unless there's a reason to believe the nego // would be successful with old version, but the update still fail // #if 0 iversion = TKEY_CURRENT_VERSION; if ( pszContext && strcmp(pszContext, FORCE_VERSION_OLD) == 0 ) { iversion = TKEY_VERSION_OLD; pszContext = NULL; } #endif // init security packet info Dns_InitSecurityPacketInfo( &secPack, NULL ); // // loop // - get valid security context // - connect to server // - do update // // loop to allow retry with new security context if server // rejects existing one // retry = 0; while ( 1 ) { // clean up any previous connection // cache security context if negotiated one if ( retry ) { if ( pMsgSend->fTcp ) { DnsCloseConnection( pMsgSend->Socket ); } if ( psecCtxt ) { Dns_EnlistSecurityContext( psecCtxt ); psecCtxt = NULL; } } retry++; // // passed in security context? // if ( phContext ) { psecCtxt = *phContext; } // // no existing security context // - see if one is cached // - otherwise, negotiate one with server // if ( !psecCtxt ) { status = Dns_NegotiateTkeyWithServer( & psecCtxt, dwFlag, pszNameServer, aipServer, pCreds, pszContext, 0 // use current version //iversion // if need versioning ); if ( status != ERROR_SUCCESS ) { // note: if failed we could do a version retry here goto Cleanup; } ASSERT( psecCtxt ); } // // init XID to something fairly random // pMsgSend->MessageHead.Xid = Dns_GetRandomXid( psecCtxt ); // // DCR_PERF: nego should try UDP, if doesn't fit (attaching TSIG) TCP // especially useful down the road with OPT and large packets // // // init remote sockaddr and socket // setup receive buffer for TCP // set timeout and receive // DnsInitializeMsgRemoteSockaddr( pMsgSend, serverIp ); pMsgSend->Socket = 0; SET_MESSAGE_FOR_TCP_RECV( pMsgRecv ); if ( pMsgRecv->Timeout == 0 ) { pMsgRecv->Timeout = SECURE_UPDATE_TCP_TIMEOUT; } // // write security record with context into packet // Dns_ResetSecurityPacketInfo( &secPack ); secPack.pSecContext = psecCtxt; status = Dns_SignMessageWithGssTsig( & secPack, & pMsgSend->MessageHead, pMsgSend->pBufferEnd, & pMsgSend->pCurrent ); if ( status != ERROR_SUCCESS ) { goto Cleanup; } // // need TCP // if ( DNS_MESSAGE_CURRENT_OFFSET(pMsgSend) > DNS_RFC_MAX_UDP_PACKET_LENGTH ) { // // connect and send // try server IPs, until find one to can connect to // pMsgSend->fTcp = TRUE; for( i=0; iAddrCount; i++ ) { serverIp = aipServer->AddrArray[i]; status = Dns_OpenTcpConnectionAndSend( pMsgSend, serverIp, TRUE ); if ( status != ERROR_SUCCESS ) { if ( pMsgSend->Socket ) { Dns_CloseSocket( pMsgSend->Socket ); pMsgSend->Socket = 0; continue; } } psecCtxt->Key.IpRemote = serverIp; break; } pMsgRecv->Socket = pMsgSend->Socket; // receive response // - if successful receive, done // - if timeout continue // - other errors indicate some setup or system level // problem status = Dns_RecvTcp( pMsgRecv ); if ( status != ERROR_SUCCESS ) { // indicate error only with this server by setting RCODE pMsgRecv->MessageHead.ResponseCode = DNS_RCODE_SERVER_FAILURE; goto Cleanup; } } // // use UDP // else { pMsgSend->fTcp = FALSE; SET_MESSAGE_FOR_UDP_RECV( pMsgRecv ); status = Dns_SendAndRecvUdp( pMsgSend, pMsgRecv, 0, NULL, pNetworkInfo ); if ( status != ERROR_SUCCESS ) { goto Cleanup; } } // // verify XID match // if ( pMsgRecv->MessageHead.Xid != pMsgSend->MessageHead.Xid ) { DNS_PRINT(( "ERROR: Incorrect XID in response. Ignoring.\n" )); goto Cleanup; } // // check RCODE, if REFUSED and TSIG extended error, then may simply // need to refresh the TKEY // if ( pMsgRecv->MessageHead.ResponseCode != DNS_RCODE_NO_ERROR ) { if ( pMsgRecv->MessageHead.ResponseCode == DNS_RCODE_REFUSED ) { status = Dns_ExtractGssTsigFromMessage( & secPack, & pMsgRecv->MessageHead, DNS_MESSAGE_END(pMsgRecv) ); if ( status != ERROR_SUCCESS ) { if ( secPack.pTsigRR && secPack.pTsigRR->Data.TSIG.wError && retry==1 ) { DNSDBG( SECURITY, ( "TSIG signed query (%p) rejected with %d and\n" "\textended RCODE = %d\n" "\tretrying rebuilding new TKEY\n", pMsgSend, pMsgRecv->MessageHead.ResponseCode, secPack.pTsigRR->Data.TSIG.wError )); pMsgSend->MessageHead.AdditionalCount = 0; IF_DNSDBG( SECURITY ) { DnsDbg_Message( "Update message after reset for retry:", pMsgSend ); } Dns_FreeSecurityContext( psecCtxt ); psecCtxt = NULL; continue; } } } // if TSIG done, no point in checking signature goto Cleanup; } // extract TSIG record // shouldn't get any error status = Dns_ExtractGssTsigFromMessage( & secPack, & pMsgRecv->MessageHead, DNS_MESSAGE_END(pMsgRecv) ); if ( status != ERROR_SUCCESS ) { DNS_PRINT(( "ERROR: TSIG parse failed on NO_ERROR response!\n" )); //ASSERT( FALSE ); break; } // verify server signature status = Dns_VerifySignatureOnPacket( &secPack ); if ( status != ERROR_SUCCESS ) { // DCR_LOG: log event -- been hacked or misbehaving server // or bad bytes in transit DNS_PRINT(( "ERROR: signature verification failed on update\n" "\tto server %s\n", IP_STRING( serverIp ) )); } break; } // // check response code // - consider some response codes // switch( status ) { case ERROR_SUCCESS: status = Dns_MapRcodeToStatus( pMsgRecv->MessageHead.ResponseCode ); break; case ERROR_TIMEOUT: DNS_PRINT(( "ERROR: connected to server at %s\n" "\tbut no response to packet at %p\n", MSG_REMOTE_IP_STRING( pMsgSend ), pMsgSend )); break; default: DNS_PRINT(( "ERROR: connected to server at %s to send packet %p\n" "\tbut error %d encountered on receive.\n", MSG_REMOTE_IP_STRING( pMsgSend ), pMsgSend, status )); break; } Cleanup: // // save security context? // if ( psecCtxt ) { if ( dwFlag & DNS_UPDATE_CACHE_SECURITY_CONTEXT ) { Dns_EnlistSecurityContext( psecCtxt ); if ( phContext ) { *phContext = NULL; } } else if ( phContext ) { *phContext = (HANDLE) psecCtxt; } else { Dns_FreeSecurityContext( psecCtxt ); } } if ( pMsgSend->fTcp ) { DnsCloseConnection( pMsgSend->Socket ); } // // free security packet session sub-allocations // - structure itself is on the stack Dns_CleanupSecurityPacketInfoEx( &secPack, FALSE ); #if 0 // // versioning failure retry? // if failed, reenter function forcing old version // if ( status != ERROR_SUCCESS && status != DNS_ERROR_RCODE_NOT_IMPLEMENTED && iversion != TKEY_VERSION_OLD ) { DNS_PRINT(( "SecureUpdate failed with status == %d\n" "\tRetrying forcing version %d signing.\n", status, TKEY_VERSION_OLD )); status = Dns_DoSecureUpdate( pMsgSend, pMsgRecv, phContext, dwFlag, pNetworkInfo, aipServer, pszNameServer, pCreds, FORCE_VERSION_OLD ); } #endif return( status ); } // // Server security session routines // DNS_STATUS Dns_FindSecurityContextFromAndVerifySignature( OUT PHANDLE phContext, IN IP_ADDRESS IpRemote, IN PDNS_HEADER pMsgHead, IN PCHAR pMsgEnd ) /*++ Routine Description: Find security context associated with TSIG and verify the signature. Arguments: phContext -- addr to receive context handle IpRemote -- IP of remote machine pMsgHead -- ptr to message head pMsgEnd -- ptr to message end (byte past end) Return Value: ERROR_SUCCESS if successful. ErrorCode on failure. --*/ { DNS_STATUS status; DNS_SECCTXT_KEY key; PSEC_CNTXT psecCtxt = NULL; PSECPACK psecPack = NULL; DNSDBG( SECURITY, ( "Dns_FindSecurityContextFromAndVerifySignature()\n" )); // security must already be running to have negotiated a TKEY if ( !g_fSecurityPackageInitialized ) { status = Dns_StartServerSecurity(); if ( status != ERROR_SUCCESS ) { return( DNS_RCODE_SERVER_FAILURE ); } } // // read TSIG from packet // psecPack = Dns_CreateSecurityPacketInfo(); if ( !psecPack ) { return( DNS_RCODE_SERVER_FAILURE ); } status = Dns_ExtractGssTsigFromMessage( psecPack, pMsgHead, pMsgEnd ); if ( status != ERROR_SUCCESS ) { goto Cleanup; } // // find existing security context // - TSIG name node // - client IP // together specify context // RtlZeroMemory( &key, sizeof(key) ); key.pszTkeyName = psecPack->pszContextName; key.IpRemote = IpRemote; psecCtxt = Dns_DequeueSecurityContextByKey( key, TRUE ); if ( !psecCtxt ) { DNSDBG( SECURITY, ( "Desired security context %s %s is NOT cached.\n" "\treturning BADKEY\n", key.pszTkeyName, IP_STRING( key.IpRemote ) )); status = DNS_ERROR_RCODE_BADKEY; SecTsigBadKey++; goto Cleanup; } // attach context to session info psecPack->pSecContext = psecCtxt; // // verify signature // status = Dns_VerifySignatureOnPacket( psecPack ); if ( status != ERROR_SUCCESS ) { DNSDBG( SECURITY, ( "Verify signature failed %08x %d.\n" "\treturning BADSIG\n", status, status )); status = DNS_ERROR_RCODE_BADSIG; goto Cleanup; } Cleanup: // return security info blob // if failed delete session info, // - return security context to cache if failure is just TSIG // being invalid if ( status == ERROR_SUCCESS ) { *phContext = psecPack; } else { Dns_FreeSecurityPacketInfo( psecPack ); if ( psecCtxt ) { DNSDBG( SECURITY, ( "Re-enlisting security context at %p after TSIG verify failure.\n", psecCtxt )); Dns_EnlistSecurityContext( psecCtxt ); } } return( status ); } DNS_STATUS Dns_ServerNegotiateTkey( IN IP_ADDRESS IpRemote, IN PDNS_HEADER pMsgHead, IN PCHAR pMsgEnd, IN PCHAR pMsgBufEnd, IN BOOL fBreakOnAscFailure, OUT PCHAR * ppCurrent ) /*++ Routine Description: Negotiate TKEY with client. Arguments: Return Value: ERROR_SUCCESS if successful. ErrorCode on failure. DCR_CLEANUP: note this is currently returning RCODEs not status. --*/ { DNS_STATUS status; SECPACK secPack; DNS_SECCTXT_KEY key; PSEC_CNTXT psecCtxt = NULL; PSEC_CNTXT ppreviousContext = NULL; WORD extRcode = 0; DNSDBG( SECURITY, ( "Dns_ServerNegotiateTkey()\n" )); // security must already be running to have negotiated a TKEY if ( !g_fSecurityPackageInitialized ) { return( DNS_RCODE_REFUSED ); } // // read TKEY from packet // Dns_InitSecurityPacketInfo( &secPack, NULL ); status = Dns_ExtractGssTkeyFromMessage( & secPack, pMsgHead, pMsgEnd, TRUE ); // fIsServer if ( status != ERROR_SUCCESS ) { DNSDBG( SECURITY, ( "TKEY Extract failed for msg at %p\n" "\tstatus = %d (%08x)\n", pMsgHead, status, status )); goto Cleanup; } // // find existing security context from this client // - client IP // - TKEY record // together specify context // // if previously negotiated context, doesn't match key length from // new TKEY, then renegotiate // RtlZeroMemory( &key, sizeof(key) ); key.IpRemote = IpRemote; key.pszTkeyName = secPack.pszContextName; psecCtxt = Dns_DequeueSecurityContextByKey( key, FALSE ); if ( psecCtxt ) { ppreviousContext = psecCtxt; DNSDBG( SECURITY, ( "Found security context matching TKEY %s %s.\n", key.pszTkeyName, IP_STRING( key.IpRemote ) )); // // previously negotiated key? // // DCR_QUESTION: no client comeback after server side nego complete? // treating client coming back on server side negotiated context // as NEW context -- not sure this is correct, client may complete // and become negotiated and want to echo // // to fix we'd need to hold this issue open and see if got "echo" // in accept // if ( psecCtxt->fNegoComplete ) { DNSDBG( SECURITY, ( "WARNING: Client nego request on existing negotiated context:\n" "\tTKEY %s\n" "\tIP %s\n", key.pszTkeyName, IP_STRING( key.IpRemote ) )); // // for Win2K (through whistler betas) allow clobbering nego // // DCR: pull Whistler Beta support for Win2001 server ship? // against would be JDP deployed whister client\servers // with this? should be zero by server ship // if ( psecCtxt->Version == TKEY_VERSION_W2K || psecCtxt->Version == TKEY_VERSION_WHISTLER_BETA ) { DNSDBG( SECURITY, ( "WIN2K context -- overwriting negotiated security context\n" "\twith new negotiation.\n" )); psecCtxt = NULL; } // post-Win2K clients should ALWAYS send with a new name // nego attempts on negotiated context are attacks // // DCR: again client echo issue here else { DNSDBG( SECURITY, ( "ERROR: post-Win2K client nego request on existing key.\n" "\terroring with BADKEY!\n" )); DNS_ASSERT( FALSE ); psecCtxt = NULL; status = DNS_ERROR_RCODE_BADKEY; extRcode = DNS_RCODE_BADKEY; goto Cleanup; } } } // // if context not found, create one // - tag it with version of TKEY found // if ( !psecCtxt ) { psecCtxt = Dns_FindOrCreateSecurityContext( key ); if ( !psecCtxt ) { status = DNS_ERROR_NO_MEMORY; goto Cleanup; } psecCtxt->Version = secPack.TkeyVersion; } // // have context -- attach to security session // secPack.pSecContext = psecCtxt; // // accept this security context // if continue needed, then write response TKEY using // // DCR_ENHANCE: in COMPLETE_AND_CONTINUE case should be adding TSIG signing // need to break out this response from ServerAcceptSecurityContext // status = Dns_ServerAcceptSecurityContext( &secPack, fBreakOnAscFailure ); if ( status != ERROR_SUCCESS ) { if ( status != DNS_STATUS_CONTINUE_NEEDED ) { DNSDBG( ANY, ( "FAILURE: ServerAcceptSecurityContext failed status=%d\n", status )); status = DNS_ERROR_RCODE_BADKEY; goto Cleanup; } status = Dns_WriteGssTkeyToMessage( &secPack, pMsgHead, pMsgBufEnd, ppCurrent, TRUE ); // fIsServer if ( status != ERROR_SUCCESS ) { status = DNS_RCODE_SERVER_FAILURE; goto Cleanup; } // // sign packet if we are now completed // if ( psecCtxt->fNegoComplete ) { goto Sign; } goto Cleanup; } // // verify signature, if present // status = Dns_ExtractGssTsigFromMessage( &secPack, pMsgHead, pMsgEnd ); if ( status == ERROR_SUCCESS ) { status = Dns_VerifySignatureOnPacket( &secPack ); if ( status != ERROR_SUCCESS ) { DNSDBG( SECURITY, ( "Verify signature failed on TKEY nego packet %p.\n" "\tstatus = %d (%08x)\n" "\treturning BADSIG\n", pMsgHead, status, status )); status = DNS_ERROR_RCODE_BADSIG; extRcode = DNS_RCODE_BADSIG; } } else if ( status == DNS_STATUS_PACKET_UNSECURE ) { status = ERROR_SUCCESS; } else { extRcode = DNS_RCODE_BADSIG; } // // sign server's response // Sign: DNSDBG( SECURITY, ( "Signing TKEY nego packet at %p after nego complete\n" "\tstatus = %d (%08x)\n" "\textRcode = %d\n", pMsgHead, status, status, extRcode )); pMsgHead->IsResponse = TRUE; status = Dns_SignMessageWithGssTsig( &secPack, pMsgHead, pMsgBufEnd, ppCurrent ); if ( status != ERROR_SUCCESS ) { DNS_PRINT(( "ERROR: failed to sign successful TKEY nego packet at %p\n" "\tstatus = %d (%08x)\n", pMsgHead, status, status )); status = ERROR_SUCCESS; } Cleanup: // // if failed, respond in in TKEY extended error field // // if extended RCODE not set above // - default to BADKEY // - unless status is extended RCODE // if ( status != ERROR_SUCCESS ) { if ( !secPack.pTkeyRR ) { status = DNS_RCODE_FORMERR; } else { if ( secPack.ExtendedRcode == 0 ) { if ( extRcode == 0 ) { extRcode = DNS_RCODE_BADKEY; if ( status > DNS_ERROR_RCODE_BADSIG && status < DNS_ERROR_RCODE_LAST ) { extRcode = (WORD)(status - DNS_ERROR_MASK); } } // write extended RCODE directly into TKEY extRCODE field // - it is a DWORD (skipping KeyLength) before Key itself INLINE_WRITE_FLIPPED_WORD( ( secPack.pTkeyRR->Data.TKEY.pKey - sizeof(DWORD) ), extRcode ); } status = DNS_RCODE_REFUSED; } } // // if successful // - whack any previous context with new context // if failed // - restore any previous context, if any // - dump any new failed context // // this lets us clients retry in any state they like, yet preserves // any existing negotiation, if this attempt was security attack or bad data // but if client successful in this negotiation, then any old context is // dumped // if ( status == ERROR_SUCCESS ) { ASSERT( secPack.pSecContext == psecCtxt ); if ( ppreviousContext != psecCtxt ) { Dns_FreeSecurityContext( ppreviousContext ); } DNSDBG( SECURITY, ( "Re-enlisting security context at %p\n", psecCtxt )); Dns_EnlistSecurityContext( psecCtxt ); } else { DNSDBG( SECURITY, ( "Failed nego context at %p\n" "\tstatus = %d\n" "\text RCODE = %d\n" "\tclient IP = %s\n" "\tTKEY name = %s\n" "\tnego complete = %d\n", psecCtxt, status, extRcode, psecCtxt ? IP_STRING( psecCtxt->Key.IpRemote ) : "NULL", psecCtxt ? psecCtxt->Key.pszTkeyName : "NULL", psecCtxt ? psecCtxt->fNegoComplete : 0 )); // free any new context that failed in nego -- if any if ( psecCtxt ) { Dns_FreeSecurityContext( psecCtxt ); } // // reenlist any previously negotiated context // // the reenlistment protects against denial of service attack // that spoofs client and attempts to trash their context, // either during nego or after completed // // however, must dump Win2K contexts as clients can reuse // the TKEY name and may NOT have saved the context; this // produces BADKEY from AcceptSecurityContext() and must // cause server to dump to reopen TKEY name to client // // DCR_QUESTION: is it possible to "reuse" partially nego'd context // that fails further negotiation // in other words can we protect against DOS attack in the middle // of nego that tries to message with nego, by requeuing the context // so real nego can complete? if ( ppreviousContext && ppreviousContext != psecCtxt ) { DNS_ASSERT( ppreviousContext->fNegoComplete ); DNSDBG( ANY, ( "WARNING: reenlisting security context %p after failed nego\n" "\tthis indicates client problem OR security attack!\n" "\tclient IP = %s\n" "\tTKEY name = %s\n" "\tnego complete = %d\n", ppreviousContext, IP_STRING( ppreviousContext->Key.IpRemote ), ppreviousContext->Key.pszTkeyName, ppreviousContext->fNegoComplete )); Dns_EnlistSecurityContext( ppreviousContext ); } } // cleanup security packet info // - parsed records, buffers, etc // - stack struct, no free Dns_CleanupSecurityPacketInfoEx( &secPack, FALSE ); return( status ); } VOID Dns_CleanupSessionAndEnlistContext( IN OUT HANDLE hSession ) /*++ Routine Description: Cleanup security session and return context to cache. Arguments: hSession -- session handle (security packet info) Return Value: None --*/ { PSECPACK psecPack = (PSECPACK) hSession; DNSDBG( SECURITY, ( "Dns_CleanupSessionAndEnlistContext( %p )\n", psecPack )); // reenlist security context Dns_EnlistSecurityContext( psecPack->pSecContext ); // cleanup security packet info // - parsed records, buffers, etc // - since handle based, this is free structure Dns_CleanupSecurityPacketInfoEx( psecPack, TRUE ); } // // API calling context // HANDLE Dns_CreateAPIContext( IN DWORD Flags, IN PVOID Credentials OPTIONAL, IN BOOL fUnicode ) /*++ Routine Description: Initializes a DNS API context possibly associated with a particular set of credentials. Flags - Type of credentials pointed to by Credentials Credentials - a pointer to a SEC_WINNT_AUTH_IDENTITY structure that contains the user, domain, and password that is to be associated with update security contexts fUnicode - ANSI is FALSE, UNICODE is TRUE to indicate version of SEC_WINNT_AUTH_IDENTITY structure in Credentials Return Value: Returns context handle successful; otherwise NULL is returned. Structure defined at top of file looks like: typedef struct _DnsAPIContext { DWORD Flags; PVOID Credentials; struct _DnsSecurityContext * pSecurityContext; } DNS_API_CONTEXT, *PDNS_API_CONTEXT; --*/ { PDNS_API_CONTEXT pcontext; pcontext = (PDNS_API_CONTEXT) ALLOCATE_HEAP_ZERO( sizeof(DNS_API_CONTEXT) ); if ( !pcontext ) { return( NULL ); } pcontext->Flags = Flags; if ( fUnicode ) { pcontext->Credentials = Dns_AllocateAndInitializeCredentialsW( (PSEC_WINNT_AUTH_IDENTITY_W)Credentials ); } else { pcontext->Credentials = Dns_AllocateAndInitializeCredentialsA( (PSEC_WINNT_AUTH_IDENTITY_A)Credentials ); } pcontext->pSecurityContext = NULL; return( (HANDLE)pcontext ); } VOID Dns_FreeAPIContext( IN OUT HANDLE hContextHandle ) /*++ Routine Description: Cleans up DNS API context data. Arguments: hContext -- handle to context to clean up Return Value: TRUE if successful FALSE otherwise Structure defined at top of file looks like: typedef struct _DnsAPIContext { DWORD Flags; PVOID Credentials; struct _DnsSecurityContext * pSecurityContext; } DNS_API_CONTEXT, *PDNS_API_CONTEXT; --*/ { PDNS_API_CONTEXT pcontext = (PDNS_API_CONTEXT)hContextHandle; if ( !pcontext ) { return; } if ( pcontext->Credentials ) { Dns_FreeAuthIdentityCredentials( pcontext->Credentials ); } if ( pcontext->pSecurityContext ) { Dns_FreeSecurityContext( pcontext->pSecurityContext ); pcontext->pSecurityContext = NULL; } FREE_HEAP( pcontext ); } PVOID Dns_GetApiContextCredentials( IN HANDLE hContextHandle ) /*++ Routine Description: returns pointer to credentials in context handle Arguments: hContext -- handle to context to clean up Return Value: TRUE if successful FALSE otherwise Structure defined at top of file looks like: typedef struct _DnsAPIContext { DWORD Flags; PVOID Credentials; struct _DnsSecurityContext * pSecurityContext; } DNS_API_CONTEXT, *PDNS_API_CONTEXT; --*/ { PDNS_API_CONTEXT pcontext = (PDNS_API_CONTEXT)hContextHandle; return pcontext ? pcontext->Credentials : NULL; } DWORD Dns_GetCurrentRid( VOID ) /*++ Routine Description: Get RID. This is used as unique ID for tagging security context. Arguments: None Return Value: Current RID if successful. (-1) on error. --*/ { BOOL bstatus; DNS_STATUS status = ERROR_SUCCESS; HANDLE hToken = NULL; PTOKEN_USER puserToken = NULL; DWORD size; UCHAR SubAuthCount; DWORD rid = (DWORD)-1; // // get thread/process token // bstatus = OpenThreadToken( GetCurrentThread(), // thread pseudo handle TOKEN_QUERY, // query info TRUE, // open as self & hToken ); // returned handle if ( !bstatus ) { DNSDBG( SECURITY, ( "Note <%lu>: failed to open thread token\n", GetLastError())); // // attempt to open process token // - if not impersonating, this is fine // bstatus = OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, & hToken ); if ( !bstatus ) { status = GetLastError(); DNSDBG( SECURITY, ( "Error <%lu>: failed to open process token\n", status )); ASSERT( FALSE ); goto Cleanup; } } // // get length required for TokenUser // - specify buffer length of 0 // bstatus = GetTokenInformation( hToken, TokenUser, NULL, 0, & size ); status = GetLastError(); if ( bstatus || status != ERROR_INSUFFICIENT_BUFFER ) { DNSDBG( SECURITY, ( "Error <%lu>: unexpected error for token info\n", status )); ASSERT( FALSE ); goto Cleanup; } // // allocate user token // puserToken = (PTOKEN_USER) ALLOCATE_HEAP( size ); if ( !puserToken ) { status = GetLastError(); DNSDBG( SECURITY, ( "Error <%lu>: failed to allocate memory\n", status )); ASSERT( FALSE ); goto Cleanup; } // // get SID of process token. // bstatus = GetTokenInformation( hToken, TokenUser, puserToken, size, & size ); if ( !bstatus ) { status = GetLastError(); DNSDBG( SECURITY, ( "Error <%lu>: failed to get user info\n", status)); ASSERT( FALSE ); goto Cleanup; } // // calculate the size of the domain sid // SubAuthCount = *GetSidSubAuthorityCount( puserToken->User.Sid ); status = GetLastError(); if ( status != ERROR_SUCCESS || SubAuthCount < 1 ) { DNSDBG( SECURITY, ( "Error <%lu>: Invalid sid.\n", status)); ASSERT( FALSE ); goto Cleanup; } size = GetLengthSid( puserToken->User.Sid ); // // get rid from the account sid // rid = *GetSidSubAuthority( puserToken->User.Sid, SubAuthCount-1 ); status = GetLastError(); if ( status != ERROR_SUCCESS ) { DNSDBG( SECURITY, ( "Error <%lu>: Invalid sid.\n", status )); ASSERT( FALSE ); goto Cleanup; } Cleanup: if ( hToken ) { CloseHandle( hToken ); } if ( puserToken ) { FREE_HEAP( puserToken ); } return rid; } DWORD Dns_GetKeyVersion( IN PSTR pszTkeyName ) /*++ Routine Description: Get TKEY\TSIG version corresponding to a context. Arguments: pszTkeyName -- context (TSIG\TKEY owner name) Return Value: Version if found. Zero if unable to read version. --*/ { LONGLONG clientId = 0; DWORD version = 0; INT iscan; if ( !pszTkeyName ) { DNSDBG( ANY, ( "ERROR: no context to Dns_GetKeyVersion()!\n" )); ASSERT( FALSE ); return( 0 ); } // // Versioned contexts have format <64bits>-ms- // iscan = sscanf( pszTkeyName, "%I64d-ms-%d", & clientId, & version ); if ( iscan != 2 ) { // // Clients before Whistler RC2 use "MS" instead of "ms". // iscan = sscanf( pszTkeyName, "%I64d-MS-%d", & clientId, & version ); } if ( iscan == 2 ) { DNSDBG( SECURITY, ( "Dns_GetKeyVersion() extracted version %d\n", version )); } else { DNSDBG( SECURITY, ( "Dns_GetKeyVersion() unable to extract version from %s\n" "\treturning 0 as version\n", pszTkeyName )); version = 0; } return version; } DNS_STATUS BuildCredsForPackage( OUT PSEC_WINNT_AUTH_IDENTITY_EXW pAuthOut, IN PWSTR pPackageName, IN PSEC_WINNT_AUTH_IDENTITY_W pAuthIn ) /*++ Description: Builds auth identity info blob with specific package. The purpose of this is to let us ONLY negotiate kerberos and avoid wasting bandwidth negotiating NTLM. Parameters: pAuthOut -- auth identity info pPackageName -- name of package pAuthIn -- existing package Return: ERROR_SUCCESS if successful. ErrorCode on failure. --*/ { DNSDBG( SECURITY, ( "BuildCredsForPackage( %p, %S )\n", pAuthOut, pPackageName )); // // currently don't limit passed in creds to kerberos // if ( pAuthIn ) { return ERROR_INVALID_PARAMETER; } // // auth-id with default creds // - user, domain, password all zero // - set length and version // - set package // - set flag to indicate unicode // RtlZeroMemory( pAuthOut, sizeof(*pAuthOut) ); pAuthOut->Version = SEC_WINNT_AUTH_IDENTITY_VERSION; pAuthOut->Length = sizeof(*pAuthOut); pAuthOut->Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; pAuthOut->PackageList = pPackageName; pAuthOut->PackageListLength = wcslen( pPackageName ); return ERROR_SUCCESS; } DNS_STATUS Dns_AcquireCredHandle( OUT PCredHandle pCredHandle, IN BOOL fDnsServer, IN PCHAR pCreds ) /*++ Routine Description: Acquire credentials handle. Cover to handle issues like kerberos restriction. Arguments: fDnsServer -- TRUE if DNS server process; FALSE otherwise pCreds -- credentials Return Value: success: ERROR_SUCCESS --*/ { SEC_WINNT_AUTH_IDENTITY_EXW clientCreds; SECURITY_STATUS status = ERROR_SUCCESS; PVOID pauthData = pCreds; DNSDBG( SECURITY, ( "Dns_AcquireCredHandle( %p, server=%d, pcred=%p )\n", pCredHandle, fDnsServer, pCreds )); // // kerberos for client // // if passed in creds // - just append package (if possible) // // no creds // - build creds with kerb package and all else NULL // if ( !fDnsServer && g_NegoKerberosOnly ) { if ( !pCreds ) { status = BuildCredsForPackage( & clientCreds, L"kerberos", NULL ); DNS_ASSERT( status == NO_ERROR ); if ( status == NO_ERROR ) { pauthData = &clientCreds; } } } // // acquire cred handle // status = g_pSecurityFunctionTable->AcquireCredentialsHandleW( NULL, // principal PACKAGE_NAME, fDnsServer ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, NULL, // LOGON id pauthData, // auth data NULL, // get key fn NULL, // get key arg pCredHandle, // out credentials NULL // valid forever ); if ( !SEC_SUCCESS(status) ) { DNSDBG( ANY, ( "ERROR: AcquireCredentialHandle failed!\n" "\tstatus = %08x %d\n" "\tpauthId = %p\n", status, status, pauthData )); } DNSDBG( SECURITY, ( "Leave Dns_AcquireCredHandle() => %08x\n", status )); return (DNS_STATUS) status; } DNS_STATUS Dns_RefreshSSpiCredentialsHandle( IN BOOL fDnsServer, IN PCHAR pCreds ) /*++ Routine Description: Refreshes the global credentials handle if it is expired. Calls into SSPI to acquire a new handle. Arguments: fDnsServer -- TRUE if DNS server process; FALSE otherwise pCreds -- credentials Return Value: success: ERROR_SUCCESS --*/ { SECURITY_STATUS status = ERROR_SUCCESS; DNSDBG( SECURITY, ( "RefreshSSpiCredentialsHandle( %d, pcreds=%p )\n", fDnsServer, pCreds )); EnterCriticalSection( &SecurityContextListCS ); // // DCR: need check -- if handle for same credentials and still valid // no need to fix up // if ( !SSPI_INVALID_HANDLE( &g_hSspiCredentials ) ) { // // Free previously allocated handle // status = g_pSecurityFunctionTable->FreeCredentialsHandle( &g_hSspiCredentials ); if ( !SEC_SUCCESS(status) ) { DNSDBG( ANY, ( "ERROR <%08x>: Cannot free credentials handle\n", status )); } // continue regardless. SecInvalidateHandle( &g_hSspiCredentials ); } ASSERT( SSPI_INVALID_HANDLE( &g_hSspiCredentials ) ); // // Acquire credentials // status = Dns_AcquireCredHandle( & g_hSspiCredentials, fDnsServer, pCreds ); if ( !SEC_SUCCESS(status) ) { DNS_PRINT(( "ERROR (0x%x): AcquireCredentialHandle failed: %u %p\n", status )); SecInvalidateHandle( &g_hSspiCredentials ); } LeaveCriticalSection( &SecurityContextListCS ); DNSDBG( SECURITY, ( "Exit <0x%x>: RefreshSSpiCredentialsHandle()\n", status )); return (DNS_STATUS) status; } // // Cred utils // PWSTR MakeCredKeyFromStrings( IN PWSTR pwsUserName, IN PWSTR pwsDomain, IN PWSTR pwsPassword ) /*++ Description: Allocates auth identity info and initializes pAuthIn info Parameters: pwsUserName -- user name pwsDomain -- domain name pwsPassword -- password Return: Ptr to newly create credentials. NULL on failure. --*/ { DWORD length; PWSTR pstr; DNSDBG( SECURITY, ( "Enter MakeCredKeyFromStrings()\n" "\tuser = %S\n" "\tdomain = %S\n" "\tpassword = %S\n", pwsUserName, pwsDomain, pwsPassword )); // // determine length and allocate // length = wcslen( pwsUserName ); length += wcslen( pwsDomain ); length += wcslen( pwsPassword ); length += 3; // two separators and NULL terminator pstr = ALLOCATE_HEAP( length * sizeof(WCHAR) ); if ( ! pstr ) { return NULL; } // // build cred info // wcscat( pstr, pwsDomain ); wcscat( pstr, L"\\" ); wcscpy( pstr, pwsUserName ); wcscat( pstr, L"\\" ); wcscat( pstr, pwsPassword ); DNSDBG( SECURITY, ( "Created cred string %S\n", pstr )); return pstr; } PWSTR MakeCredKey( IN PCHAR pCreds ) /*++ Description: Allocates auth identity info and initializes pAuthIn info Parameters: pCreds -- credentials Return: Ptr to newly create credentials. NULL on failure. --*/ { PSEC_WINNT_AUTH_IDENTITY_EXW pauth = NULL; SEC_WINNT_AUTH_IDENTITY_EXW dummyAuth; PWSTR pstr = NULL; DWORD length; DNSDBG( SECURITY, ( "MakeCredKey( %p )\n", pCreds )); // // determine AUTH_EX or old style credentials // - if old style dummy up new version // pauth = (PSEC_WINNT_AUTH_IDENTITY_EXW) pCreds; if ( pauth->Length == sizeof(*pauth) && pauth->Version < 0x10000 ) { DNSDBG( SECURITY, ( "Creds at %p are new AuthEx creds.\n", pCreds )); } else { DNSDBG( SECURITY, ( "Creds at %p are old style.\n", pCreds )); RtlCopyMemory( (PBYTE) &dummyAuth.User, pCreds, sizeof(SEC_WINNT_AUTH_IDENTITY_W) ); pauth = &dummyAuth; } // // sum lengths and allocate string // length = pauth->UserLength; length += pauth->DomainLength; length += pauth->PasswordLength; length += 3; pstr = ALLOCATE_HEAP( length * sizeof(WCHAR) ); if ( ! pstr ) { return NULL; } // // determine unicode \ ANSI -- write string // // it appears that with wprint functions the meaning of // %s and %S is reversed // if ( pauth->Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE ) { swprintf( pstr, //L"%S\\%S\\%S", L"%s\\%s\\%s", pauth->Domain, pauth->User, pauth->Password ); DNSDBG( SECURITY, ( "Created cred string %S from unicode\n", pstr )); } else { swprintf( pstr, //L"%s\\%s\\%s", L"%S\\%S\\%S", pauth->Domain, pauth->User, pauth->Password ); DNSDBG( SECURITY, ( "Created cred string %S from ANSI\n", pstr )); } return pstr; } BOOL CompareCredKeys( IN PWSTR pwsCredKey1, IN PWSTR pwsCredKey2 ) /*++ Description: Compare cred strings for matching security contexts. Parameters: pwsCredKey1 -- cred string pwsCredKey2 -- cred string Return: TRUE if match. FALSE if no match. --*/ { DNSDBG( SECURITY, ( "CompareCredKeys( %S, %S )\n", pwsCredKey1, pwsCredKey2 )); // // most common case -- no creds // if ( !pwsCredKey1 || !pwsCredKey2 ) { return( pwsCredKey2==pwsCredKey1 ); } // // cred strings are wide character strings // - just string compare // return( wcscmp( pwsCredKey1, pwsCredKey2 ) == 0 ); } // // End security.c //