mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7512 lines
179 KiB
7512 lines
179 KiB
/*++
|
|
|
|
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 <domain>\<machine>$
|
|
|
|
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; i<aipServer->AddrCount; 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; i<aipServer->AddrCount; 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; i<aipServer->AddrCount; 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-<version#>
|
|
//
|
|
|
|
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
|
|
//
|