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