Windows-Server-2003/inetcore/digest/digest.cxx

655 lines
19 KiB
C++

/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
digest.cxx
Abstract:
Parses http digest challenges and generates http digest
authorization headers for digest sspi package.
Author:
Adriaan Canter (adriaanc) 01-Aug-1998
--*/
#include "include.hxx"
// HTTP related defines
#define HEADER_IDX 0
#define REALM_IDX 1
#define HOST_IDX 2
#define URL_IDX 3
#define METHOD_IDX 4
#define USER_IDX 5
#define PASS_IDX 6
#define NONCE_IDX 7
#define NC_IDX 8
#define HWND_IDX 9
#define NUM_BUFF 10
// POP related defines.
#define POP_USER_IDX 1
#define POP_PASS_IDX 2
#define NUM_EXTENDED_POP_BUFFERS 3
// Used for generating response line.
#define FLAG_QUOTE 0x1
#define FLAG_TERMINATE 0x2
//--------------------------------------------------------------------
// CDigest:: ToHex
//
// Routine Description:
//
// Convert binary data to ASCII hex representation
//
// Arguments:
//
// pSrc - binary data to convert
// cSrc - length of binary data
// pDst - buffer receiving ASCII representation of pSrc
//
//--------------------------------------------------------------------
VOID CDigest::ToHex(LPBYTE pSrc, UINT cSrc, LPSTR pDst)
{
UINT x;
UINT y;
// BUGBUG - character case issue ?
#define TOHEX(a) ((a)>=10 ? 'a'+(a)-10 : '0'+(a))
for ( x = 0, y = 0 ; x < cSrc ; ++x )
{
UINT v;
v = pSrc[x]>>4;
pDst[y++] = TOHEX( v );
v = pSrc[x]&0x0f;
pDst[y++] = TOHEX( v );
}
pDst[y] = '\0';
}
//--------------------------------------------------------------------
// AddDigestHeader
//--------------------------------------------------------------------
BOOL AddDigestHeader(LPSTR szHeader, LPDWORD pcbHeader,
LPSTR szValue, LPSTR szData,
DWORD cbAlloced, DWORD dwFlags)
{
DWORD cbValue, cbData, cbRequired;
cbValue = strlen(szValue);
cbData = strlen(szData);
cbRequired = *pcbHeader
+ cbValue + cbData + sizeof('=') + 2 * sizeof('\"') + sizeof(", ") - 1;
if (cbRequired > cbAlloced)
return FALSE;
memcpy(szHeader + *pcbHeader, szValue, cbValue);
(*pcbHeader) += cbValue;
memcpy(szHeader + *pcbHeader, "=", sizeof('='));
(*pcbHeader) += sizeof('=');
if (dwFlags & FLAG_QUOTE)
{
memcpy(szHeader + *pcbHeader, "\"", sizeof('\"'));
(*pcbHeader) += sizeof('\"');
}
memcpy(szHeader + *pcbHeader, szData, cbData);
(*pcbHeader) += cbData;
if (dwFlags & FLAG_QUOTE)
{
memcpy(szHeader + *pcbHeader, "\"", sizeof('\"'));
(*pcbHeader) += sizeof('\"');
}
if (!(dwFlags & FLAG_TERMINATE))
{
memcpy(szHeader + *pcbHeader, ", ", sizeof(", "));
(*pcbHeader) += (sizeof(", ") - 1);
}
else
{
*(szHeader + *pcbHeader) = '\0';
}
return TRUE;
}
//--------------------------------------------------------------------
// CDigest::CDigest()
//--------------------------------------------------------------------
CDigest::CDigest()
{}
//--------------------------------------------------------------------
// CDigest::MakeCNonce
//--------------------------------------------------------------------
LPSTR CDigest::MakeCNonce()
{
DWORD dwRand;
static DWORD dwCounter;
LPSTR szCNonce;
szCNonce = new CHAR[SIZE_MD5_DIGEST+1];
if (!szCNonce)
{
DIGEST_ASSERT(FALSE);
return NULL;
}
dwRand = (GetTickCount() * rand()) + dwCounter++;
MD5_CTX md5ctx;
MD5Init (&md5ctx);
MD5Update(&md5ctx, (LPBYTE) &dwRand, sizeof(DWORD));
MD5Final (&md5ctx);
ToHex(md5ctx.digest, sizeof(md5ctx.digest), szCNonce);
return szCNonce;
}
//--------------------------------------------------------------------
// CDigest::ParseChallenge
//--------------------------------------------------------------------
DWORD CDigest::ParseChallenge(CSess * pSess, PSecBufferDesc pSecBufDesc,
CParams **ppParams, DWORD fContextReq)
{
// SecBufferDesc looks like
//
// [ulversion][cbuffers][pbuffers]
// |
// --------------------------
// |
// |--> [cbBuffer][buffertype][lpvoid][cbBuffer]...
//
DWORD cbQop, cbAlgo, dwError = ERROR_SUCCESS;
CHAR *szQop, *szAlgo, *ptr;
HWND *phWnd;
LPDWORD pcNC;
LPSTR szHeader, szRealm, szHost, szUrl,
szMethod, szUser, szPass, szNonce;
BOOL fPreAuth = FALSE, fCredsSupplied = FALSE;
// Identify buffer components.
// Legacy client.
if (!pSess->fHTTP)
{
szHeader = (LPSTR) pSecBufDesc->pBuffers[HEADER_IDX].pvBuffer;
szRealm = NULL;
szHost = "";
szUrl = "";
szMethod = "AUTHENTICATE";
if (pSecBufDesc->cBuffers == NUM_EXTENDED_POP_BUFFERS)
szUser = (LPSTR) pSecBufDesc->pBuffers[POP_USER_IDX].pvBuffer;
else
szUser = NULL;
if (pSecBufDesc->cBuffers == NUM_EXTENDED_POP_BUFFERS)
szPass = (LPSTR) pSecBufDesc->pBuffers [POP_PASS_IDX].pvBuffer;
else
szPass = NULL;
szNonce = NULL;
phWnd = NULL;
}
// Current client. Leave room for extra
// param (pRequest).
else if (pSess->fHTTP
&& (pSecBufDesc->cBuffers == NUM_BUFF
|| pSecBufDesc->cBuffers == NUM_BUFF+1))
{
szHeader = (LPSTR) pSecBufDesc->pBuffers[HEADER_IDX].pvBuffer;
szRealm = (LPSTR) pSecBufDesc->pBuffers [REALM_IDX].pvBuffer;
szHost = (LPSTR) pSecBufDesc->pBuffers [HOST_IDX].pvBuffer;
szUrl = (LPSTR) pSecBufDesc->pBuffers [URL_IDX].pvBuffer;
szMethod = (LPSTR) pSecBufDesc->pBuffers[METHOD_IDX].pvBuffer;
szUser = (LPSTR) pSecBufDesc->pBuffers [USER_IDX].pvBuffer;
szPass = (LPSTR) pSecBufDesc->pBuffers [PASS_IDX].pvBuffer;
szNonce = (LPSTR) pSecBufDesc->pBuffers [NONCE_IDX].pvBuffer;
pcNC =(DWORD*) pSecBufDesc->pBuffers [ NC_IDX].pvBuffer;
phWnd = (HWND*) pSecBufDesc->pBuffers [HWND_IDX].pvBuffer;
}
else
{
dwError = ERROR_INVALID_PARAMETER;
goto exit;
}
// Validate parameters.
dwError = ERROR_INVALID_PARAMETER;
if (szHost && szUrl && szMethod)
{
// Auth or UI prompt to challenge for any user.
if (szHeader && !szRealm && !szUser && !szPass && !szNonce)
dwError = ERROR_SUCCESS;
// Auth or UI prompt to challenge for particular user.
else if (szHeader && !szRealm && szUser && !szPass && !szNonce)
dwError = ERROR_SUCCESS;
// Auth to challenge with creds supplied.
else if (szHeader && !szRealm && szUser
&& szPass && !szNonce && (fContextReq & ISC_REQ_USE_SUPPLIED_CREDS))
{
fCredsSupplied = TRUE;
dwError = ERROR_SUCCESS;
}
// Preauth with realm supplied for any user.
else if (!szHeader && szRealm && !szUser && !szPass && !szNonce)
{
fPreAuth = TRUE;
dwError = ERROR_SUCCESS;
}
// Preauth with realm supplied for a particular user.
else if (!szHeader && szRealm && szUser && !szPass && !szNonce)
{
fPreAuth = TRUE;
dwError = ERROR_SUCCESS;
}
// Preauth with realm and creds supplied.
else if (!szHeader && szRealm && szUser &&
szPass && szNonce && pcNC && (fContextReq & ISC_REQ_USE_SUPPLIED_CREDS))
{
fPreAuth = TRUE;
fCredsSupplied = TRUE;
dwError = ERROR_SUCCESS;
}
}
// Fail if buffers did not fall into one
// of the acceptable formats.
if (dwError != ERROR_SUCCESS)
goto exit;
// Construct the params object.
*ppParams = new CParams(szHeader);
if (!*ppParams)
{
DIGEST_ASSERT(FALSE);
dwError = ERROR_NOT_ENOUGH_MEMORY;
goto exit;
}
// Flag if this is preauth and/or if
// credentials are supplied.
(*ppParams)->SetPreAuth(fPreAuth);
(*ppParams)->SetCredsSupplied(fCredsSupplied);
// Only set algorithm if auth to challenge or UI prompting.
if (!(*ppParams)->IsPreAuth())
{
// Algorithm should be MD5 or MD5-sess, default to MD5 if not specified.
if (!(*ppParams)->GetParam(CParams::ALGORITHM, &szAlgo, &cbAlgo))
{
(*ppParams)->SetParam(CParams::ALGORITHM, "MD5", sizeof("MD5") - 1);
(*ppParams)->SetMd5Sess(FALSE);
}
else if (szAlgo && !lstrcmpi(szAlgo, "Md5-sess"))
{
(*ppParams)->SetMd5Sess(TRUE);
}
else if (szAlgo && !lstrcmpi(szAlgo, "MD5"))
{
(*ppParams)->SetMd5Sess(FALSE);
}
else
{
// Not md5 or md5-sess
// DIGEST_ASSERT(FALSE);
dwError = ERROR_INVALID_PARAMETER;
goto exit;
}
}
// If this is an imap/pop client we require
// md5-sess specified in the challenge.
if (!pSess->fHTTP && !(*ppParams)->IsMd5Sess())
{
DIGEST_ASSERT(FALSE);
dwError = ERROR_INVALID_PARAMETER;
goto exit;
}
// Always set host, url, method (required). User and pass optional.
if (! (*ppParams)->SetParam(CParams::HOST, szHost, szHost ? strlen(szHost) : 0)
|| !(*ppParams)->SetParam(CParams::URL, szUrl, szUrl ? strlen(szUrl) : 0)
|| !(*ppParams)->SetParam(CParams::METHOD, szMethod, szMethod ? strlen(szMethod) : 0)
|| !(*ppParams)->SetParam(CParams::USER, szUser, szUser ? strlen(szUser) : 0)
|| !(*ppParams)->SetParam(CParams::PASS, szPass, szPass ? strlen(szPass) : 0))
{
DIGEST_ASSERT(FALSE);
dwError = ERROR_NOT_ENOUGH_MEMORY;
goto exit;
}
// BUGBUG - do cleanup locally on failure.
// If not preauthenticating we are authenticating in response
// to a challenge or prompting for UI.
if (!(*ppParams)->IsPreAuth())
{
// Check to see that qop=auth is specified
(*ppParams)->GetParam(CParams::QOP, &szQop, &cbQop);
if (!(szQop && (*ppParams)->FindToken(szQop, cbQop+1, AUTH_SZ, AUTH_LEN, NULL)))
{
DIGEST_ASSERT(FALSE);
dwError = ERROR_INVALID_PARAMETER;
goto exit;
}
// Save off any hWnd for UI (only needed for challenges).
if (fContextReq & ISC_REQ_PROMPT_FOR_CREDS)
{
(*ppParams)->SetHwnd(phWnd);
}
}
// Otherwise we are attempting to preauthenticate.
else
{
// Set the realm for preauth.
if (!(*ppParams)->SetParam(CParams::REALM, szRealm, strlen(szRealm)))
{
DIGEST_ASSERT(FALSE);
dwError = ERROR_INVALID_PARAMETER;
goto exit;
}
// Also set the nonce if preauth + use supplied creds.
if (fContextReq & ISC_REQ_USE_SUPPLIED_CREDS)
{
(*ppParams)->SetNC(pcNC);
if (!(*ppParams)->SetParam(CParams::NONCE, szNonce, strlen(szNonce)))
{
DIGEST_ASSERT(FALSE);
dwError = ERROR_INVALID_PARAMETER;
goto exit;
}
}
}
exit:
return dwError;
}
//--------------------------------------------------------------------
// CDigest::GenerateResponse
//--------------------------------------------------------------------
DWORD CDigest::GenerateResponse(CSess *pSess, CParams *pParams,
CCredInfo *pInfo, PSecBufferDesc pSecBufDesc)
{
// bugbug - psz's on these.
LPSTR szBuf, szMethod, szUrl, szNonce, szCNonce, szCNonceSess, szOpaque;
DWORD *pcbBuf, cbMethod, cbUrl, cbNonce, cbCNonce, cbCNonceSess, cbOpaque;
DWORD dwError, cbAlloced;
BOOL fSess = FALSE;
szBuf = (LPSTR) pSecBufDesc->pBuffers[0].pvBuffer;
pcbBuf = &(pSecBufDesc->pBuffers[0].cbBuffer);
cbAlloced = *pcbBuf;
*pcbBuf = 0;
szCNonce = NULL;
if (!cbAlloced)
{
// Modern clients better pass in
// the size of the output buffer.
if (pSess->fHTTP)
{
DIGEST_ASSERT(FALSE);
dwError = ERROR_INVALID_PARAMETER;
goto quit;
}
else
// Legacy clients like OE don't, so
// we allow up to 64k.
cbAlloced = PACKAGE_MAXTOKEN;
}
CHAR szA1[SIZE_MD5_DIGEST + 1],
szA2[SIZE_MD5_DIGEST + 1],
szH [SIZE_MD5_DIGEST + 1];
MD5_CTX md5a1, md5a2, md5h;
// Get method and request-uri.
pParams->GetParam(CParams::METHOD, &szMethod, &cbMethod);
if (pSess->fHTTP)
pParams->GetParam(CParams::URL, &szUrl, &cbUrl);
else
{
// request-uri is empty string for legacy clients.
szUrl = "";
cbUrl = 0;
}
// Must have both method and request-uri.
if (!szMethod || !szUrl)
{
DIGEST_ASSERT(FALSE);
dwError = ERROR_INVALID_PARAMETER;
goto quit;
}
// Opaque is optional.
pParams->GetParam(CParams::_OPAQUE, &szOpaque, &cbOpaque);
// Unless we are preauthenticating it is possible that
// the credential does not have a nonce value if the
// credential was established via ApplyControlToken.
// In this case we use the nonce received in the challenge.
if (pInfo->szNonce)
{
szNonce = pInfo->szNonce;
cbNonce = strlen(szNonce);
}
else if (!pParams->GetParam(CParams::NONCE, &szNonce, &cbNonce))
{
DIGEST_ASSERT(FALSE);
dwError = ERROR_INVALID_PARAMETER;
goto quit;
}
// Existance of a client nonce in the credential
// implies md5-sess. Otherwise we need to create one.
if (pInfo->szCNonce)
{
szCNonceSess = pInfo->szCNonce;
cbCNonceSess = SIZE_MD5_DIGEST;
if (pInfo->cCount == 1)
szCNonce = pInfo->szCNonce;
else
szCNonce = MakeCNonce();
cbCNonce = SIZE_MD5_DIGEST;
fSess = TRUE;
}
// No client nonce means we simply
// generate one now.
else
{
szCNonce = MakeCNonce();
cbCNonce = SIZE_MD5_DIGEST;
szCNonceSess = NULL;
cbCNonceSess = 0;
fSess = FALSE;
}
// Encode nonce count.
// BUGBUG - wsprintf returns strlen
// and are any cruntime deps.
CHAR szNC[16];
DWORD cbNC;
wsprintf(szNC, "%08x", pInfo->cCount);
cbNC = strlen(szNC);
LPSTR szPass = pInfo->GetPass();
// 1) Md5(user:realm:pass) or
// Md5(Md5(user:realm:pass):nonce:cnoncesess)
MD5Init (&md5a1);
MD5Update(&md5a1, (LPBYTE) pInfo->szUser, strlen(pInfo->szUser));
MD5Update(&md5a1, (LPBYTE) ":", 1);
MD5Update(&md5a1, (LPBYTE) pInfo->szRealm, strlen(pInfo->szRealm));
MD5Update(&md5a1, (LPBYTE) ":", 1);
MD5Update(&md5a1, (LPBYTE) szPass, (szPass ? strlen(szPass) : 0));
if (szPass)
{
SecureZeroMemory(szPass, strlen(szPass));
delete [] szPass;
szPass = NULL;
}
if (fSess)
{
// Md5(Md5(user:realm:pass):nonce:cnoncesess)
MD5Final (&md5a1);
ToHex(md5a1.digest, sizeof(md5a1.digest), szA1);
MD5Init (&md5a1);
MD5Update(&md5a1, (LPBYTE) szA1, SIZE_MD5_DIGEST);
MD5Update(&md5a1, (LPBYTE) ":", 1);
MD5Update(&md5a1, (LPBYTE) szNonce, cbNonce);
MD5Update(&md5a1, (LPBYTE) ":", 1);
MD5Update(&md5a1, (LPBYTE) szCNonceSess, cbCNonceSess);
}
MD5Final (&md5a1);
ToHex(md5a1.digest, sizeof(md5a1.digest), szA1);
// 2) Md5(method:url)
MD5Init (&md5a2);
MD5Update(&md5a2, (LPBYTE) szMethod, cbMethod);
MD5Update(&md5a2, (LPBYTE) ":", 1);
MD5Update(&md5a2, (LPBYTE) szUrl, cbUrl);
MD5Final (&md5a2);
ToHex(md5a2.digest, sizeof(md5a2.digest), szA2);
// 3) Md5(A1:nonce:nc:cnonce:qop:A2)
MD5Init (&md5h);
MD5Update(&md5h, (LPBYTE) szA1, SIZE_MD5_DIGEST);
MD5Update(&md5h, (LPBYTE) ":", 1);
MD5Update(&md5h, (LPBYTE) szNonce, cbNonce);
MD5Update(&md5h, (LPBYTE) ":", 1);
MD5Update(&md5h, (LPBYTE) szNC, cbNC);
MD5Update(&md5h, (LPBYTE) ":", 1);
MD5Update(&md5h, (LPBYTE) szCNonce, cbCNonce);
MD5Update(&md5h, (LPBYTE) ":", 1);
MD5Update(&md5h, (LPBYTE) AUTH_SZ, AUTH_LEN);
MD5Update(&md5h, (LPBYTE) ":", 1);
MD5Update(&md5h, (LPBYTE) szA2, SIZE_MD5_DIGEST);
MD5Final (&md5h);
ToHex(md5h.digest, sizeof(md5h.digest), szH);
// http digest.
if (pSess->fHTTP)
{
if ( AddDigestHeader(szBuf, pcbBuf, "Digest username",
pInfo->szUser, cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf, "realm",
pInfo->szRealm, cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf, "qop",
"auth", cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf,
"algorithm", (pInfo->szCNonce ? "MD5-sess" : "MD5"), cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf, "uri", szUrl, cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf, "nonce", szNonce, cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf, "nc", szNC, cbAlloced, 0)
&& AddDigestHeader(szBuf, pcbBuf, "cnonce", szCNonce, cbAlloced, FLAG_QUOTE)
&& (!szOpaque || AddDigestHeader(szBuf, pcbBuf, "opaque", szOpaque, cbAlloced, FLAG_QUOTE))
&& AddDigestHeader(szBuf, pcbBuf,
"response", szH, cbAlloced, FLAG_QUOTE | FLAG_TERMINATE)
)
{
dwError = ERROR_SUCCESS;
}
else
{
dwError = ERROR_NOT_ENOUGH_MEMORY;
*pcbBuf = 0;
}
}
else
{
if ( AddDigestHeader(szBuf, pcbBuf, "Digest username",
pInfo->szUser, cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf, "realm",
pInfo->szRealm, cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf, "qop",
"auth", cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf,
"algorithm", (pInfo->szCNonce ? "MD5-sess" : "MD5"), cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf, "nonce", szNonce, cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf, "cnonce", szCNonce, cbAlloced, FLAG_QUOTE)
&& AddDigestHeader(szBuf, pcbBuf,
"response", szH, cbAlloced, FLAG_QUOTE | FLAG_TERMINATE)
)
{
dwError = ERROR_SUCCESS;
}
else
{
dwError = ERROR_NOT_ENOUGH_MEMORY;
*pcbBuf = 0;
}
}
quit:
if (szCNonce && szCNonce != pInfo->szCNonce)
delete szCNonce;
return dwError;
}