605 lines
16 KiB
C++
605 lines
16 KiB
C++
/*++
|
|
|
|
Copyright (c) 1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
DigestAuth.cpp
|
|
|
|
Abstract:
|
|
|
|
Performs digest authentication MD5 calculations.
|
|
MD5.cpp is an external dependency.
|
|
|
|
Author:
|
|
|
|
Darren L. Anderson (darrenan) 5-Aug-1998
|
|
|
|
Revision History:
|
|
|
|
5-Aug-1998 darrenan
|
|
|
|
Created.
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include <malloc.h>
|
|
#include <time.h>
|
|
#include "pperr.h"
|
|
#include "binhex.h"
|
|
|
|
typedef enum { ALGO_MD5, ALGO_MD5_SESS } DIGEST_ALGORITHM;
|
|
typedef enum { QOP_NONE, QOP_AUTH, QOP_AUTH_INT } DIGEST_QOP;
|
|
|
|
DIGEST_ALGORITHM WINAPI
|
|
AlgorithmFromString(
|
|
LPCSTR pszAlgorithm
|
|
)
|
|
{
|
|
DIGEST_ALGORITHM Algorithm;
|
|
|
|
if(pszAlgorithm == NULL)
|
|
Algorithm = ALGO_MD5;
|
|
else if(_stricmp("MD5-sess", pszAlgorithm) == 0)
|
|
Algorithm = ALGO_MD5_SESS;
|
|
else if(_stricmp("MD5", pszAlgorithm) == 0)
|
|
Algorithm = ALGO_MD5;
|
|
else
|
|
Algorithm = ALGO_MD5;
|
|
|
|
return Algorithm;
|
|
}
|
|
|
|
DIGEST_QOP WINAPI
|
|
QopFromString(
|
|
LPCSTR pszQOP
|
|
)
|
|
{
|
|
DIGEST_QOP Qop;
|
|
|
|
if(pszQOP == NULL)
|
|
Qop = QOP_NONE;
|
|
else if(strcmp("auth", pszQOP) == 0)
|
|
Qop = QOP_AUTH;
|
|
else if(strcmp("auth-int", pszQOP) == 0)
|
|
Qop = QOP_AUTH_INT;
|
|
else
|
|
Qop = QOP_NONE;
|
|
|
|
return Qop;
|
|
}
|
|
|
|
VOID WINAPI
|
|
ToHex(
|
|
LPBYTE pSrc,
|
|
UINT cSrc,
|
|
LPSTR pDst
|
|
)
|
|
|
|
/*++
|
|
|
|
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
|
|
|
|
Return Value:
|
|
|
|
Nothing
|
|
|
|
--*/
|
|
|
|
{
|
|
#define TOHEX(a) ((a)>=10 ? 'a'+(a)-10 : '0'+(a))
|
|
|
|
for ( UINT 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';
|
|
}
|
|
|
|
LONG MD5(UCHAR* pBuf, UINT nBuf, UCHAR* digest)
|
|
{
|
|
MD5_CTX context;
|
|
|
|
if(pBuf==NULL || IsBadReadPtr((CONST VOID*)pBuf, (UINT)nBuf))
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
MD5Init (&context);
|
|
MD5Update (&context, pBuf, nBuf);
|
|
MD5Final (&context);
|
|
|
|
memcpy(digest, context.digest, 16);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
HRESULT WINAPI
|
|
DigestFromCreds(
|
|
IN LPCSTR pszAlgorithm,
|
|
IN LPCSTR pszUsername,
|
|
IN LPCSTR pszRealm,
|
|
IN LPCSTR pszPassword,
|
|
IN LPCSTR pszNonce,
|
|
IN LPCSTR pszNonceCount,
|
|
IN LPCSTR pszCNonce,
|
|
IN LPCSTR pszQOP,
|
|
IN LPCSTR pszMethod,
|
|
IN LPCSTR pszURI,
|
|
IN LPCSTR pszEntityDigest,
|
|
OUT LPSTR pszSessionKey,
|
|
OUT LPSTR pszResult
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
DigestFromCreds Produces a hexadecimally encoded string containing the
|
|
Digest response given the arguments below.
|
|
|
|
Arguments:
|
|
|
|
pszAlgorithm The algorithm being used to calculate the response.
|
|
If NULL or "", assume "MD5". Possible values are
|
|
"MD5" and "MD5-Sess".
|
|
|
|
pszUsername Member's passport ID.
|
|
|
|
pszRealm Realm, should be constant.
|
|
|
|
pszPassword Member's password.
|
|
|
|
pszNonce Nonce.
|
|
|
|
pszNonceCount The Nonce Count. MUST be NULL if pszQOP is NULL or "".
|
|
Otherwise, Nonce Count is REQUIRED.
|
|
|
|
pszCNonce The Client nonce. May be NULL or "".
|
|
|
|
pszQOP The Quality of Privacy. May be NULL, "", "auth" or
|
|
"auth-int". If it's NULL or "" then RFC 2069 style
|
|
digest is being performed.
|
|
|
|
pszMethod HTTP method used in the request. REQUIRED.
|
|
|
|
pszURI Resource being requested. REQUIRED.
|
|
|
|
pszEntityDigest Entity Digest. May be NULL if qop="auth" or nothing.
|
|
REQUIRED if qop="auth-int".
|
|
|
|
pszSessionKey Session key returned to caller. Session key is MD5(A1).
|
|
|
|
pszResult Destination buffer for result. Should point to a buffer
|
|
of at least MIN_OUTPUT_BUF_LEN characters.
|
|
|
|
Return Value:
|
|
|
|
S_OK Call was successful.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
HRESULT hr;
|
|
DIGEST_ALGORITHM Algorithm;
|
|
DIGEST_QOP Qop;
|
|
CHAR achWork [512];
|
|
UCHAR achDigest [ 16];
|
|
CHAR achHashOfA1 [DIGESTBUF_LEN];
|
|
|
|
//
|
|
// Detect the algorithm and QOP.
|
|
//
|
|
|
|
Algorithm = AlgorithmFromString(pszAlgorithm);
|
|
Qop = QopFromString(pszQOP);
|
|
|
|
// Compute the digest.
|
|
|
|
//
|
|
// Build A1.
|
|
// For MD5 this is username@domain:realm:password
|
|
// For MD5-Sess this is MD5(username@domain:realm:password):nonce:cnonce
|
|
//
|
|
|
|
strcpy(achWork, pszUsername);
|
|
strcat(achWork, ":");
|
|
strcat(achWork, pszRealm);
|
|
strcat(achWork, ":");
|
|
strcat(achWork, pszPassword);
|
|
|
|
if(Algorithm == ALGO_MD5_SESS)
|
|
{
|
|
// Hash it.
|
|
MD5((UCHAR*)achWork, strlen(achWork), achDigest);
|
|
ToHex(achDigest, 16, achHashOfA1);
|
|
|
|
strcpy(achWork, achHashOfA1);
|
|
strcat(achWork, ":");
|
|
strcat(achWork, pszNonce);
|
|
strcat(achWork, ":");
|
|
strcat(achWork, pszCNonce);
|
|
}
|
|
|
|
// Hash it.
|
|
MD5((UCHAR*)achWork, strlen(achWork), achDigest);
|
|
ToHex(achDigest, 16, achHashOfA1);
|
|
|
|
hr = DigestFromKey(
|
|
pszAlgorithm,
|
|
achHashOfA1,
|
|
pszNonce,
|
|
pszNonceCount,
|
|
pszCNonce,
|
|
pszQOP,
|
|
pszMethod,
|
|
pszURI,
|
|
pszEntityDigest,
|
|
pszResult
|
|
);
|
|
|
|
if(hr != S_OK)
|
|
goto Cleanup;
|
|
|
|
strcpy(pszSessionKey, achHashOfA1);
|
|
|
|
Cleanup:
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT WINAPI
|
|
DigestFromKey(
|
|
IN LPCSTR pszAlgorithm,
|
|
IN LPCSTR pszSessionKey,
|
|
IN LPCSTR pszNonce,
|
|
IN LPCSTR pszNonceCount,
|
|
IN LPCSTR pszCNonce,
|
|
IN LPCSTR pszQOP,
|
|
IN LPCSTR pszMethod,
|
|
IN LPCSTR pszURI,
|
|
IN LPCSTR pszEntityDigest,
|
|
OUT LPSTR pszResult
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
DigestFromCreds Produces a hexadecimally encoded string containing the
|
|
Digest response given the arguments below.
|
|
|
|
Arguments:
|
|
|
|
pszAlgorithm The algorithm being used to calculate the response.
|
|
If NULL or "", assume "MD5". Possible values are
|
|
"MD5" and "MD5-Sess".
|
|
|
|
pszSessionKey Pre-computed MD5(A1).
|
|
|
|
pszNonce Nonce.
|
|
|
|
pszNonceCount The Nonce Count. MUST be NULL if pszQOP is NULL or "".
|
|
Otherwise, Nonce Count is REQUIRED.
|
|
|
|
pszCNonce The Client nonce. May be NULL or "".
|
|
|
|
pszQOP The Quality of Privacy. May be NULL, "", "auth" or
|
|
"auth-int". If it's NULL or "" then RFC 2069 style
|
|
digest is being performed.
|
|
|
|
pszMethod HTTP method used in the request. REQUIRED.
|
|
|
|
pszURI Resource being requested. REQUIRED.
|
|
|
|
pszEntityDigest Entity Digest. May be NULL if qop="auth" or nothing.
|
|
REQUIRED if qop="auth-int".
|
|
|
|
pszResult Destination buffer for result. Should point to a buffer
|
|
of at least MIN_OUTPUT_BUF_LEN characters.
|
|
|
|
Return Value:
|
|
|
|
S_OK Call was successful.
|
|
E_POINTER A required parameter was NULL.
|
|
|
|
--*/
|
|
|
|
{
|
|
HRESULT hr;
|
|
DIGEST_ALGORITHM Algorithm;
|
|
DIGEST_QOP Qop;
|
|
CHAR achWork [512];
|
|
UCHAR achDigest [ 16];
|
|
CHAR achHashOut [ 36];
|
|
|
|
//
|
|
// Detect the algorithm and QOP.
|
|
//
|
|
|
|
Algorithm = AlgorithmFromString(pszAlgorithm);
|
|
Qop = QopFromString(pszQOP);
|
|
|
|
// Compute the digest.
|
|
|
|
//
|
|
// Build A2
|
|
// For qop="auth" this is method:uri
|
|
// For qop="auth-int" this is method:uri:entitydigest
|
|
//
|
|
|
|
strcpy(achWork, pszMethod);
|
|
strcat(achWork, ":");
|
|
strcat(achWork, pszURI);
|
|
|
|
if(Qop == QOP_AUTH_INT)
|
|
{
|
|
strcat(achWork, ":");
|
|
strcat(achWork, pszEntityDigest);
|
|
}
|
|
|
|
// Hash it.
|
|
MD5((UCHAR*)achWork, strlen(achWork), achDigest);
|
|
ToHex(achDigest, 16, achHashOut);
|
|
|
|
//
|
|
// Compute final chunk.
|
|
// For qop="" this is MD5(key:nonce:MD5(A2))
|
|
// For qop="auth" or qop="auth-int" this is
|
|
// MD5(key:nonce:nc:cnonce:qop:MD5(A2))
|
|
//
|
|
|
|
strcpy(achWork, pszSessionKey);
|
|
strcat(achWork, ":");
|
|
strcat(achWork, pszNonce);
|
|
strcat(achWork, ":");
|
|
|
|
if(Qop != QOP_NONE)
|
|
{
|
|
strcat(achWork, pszNonceCount);
|
|
strcat(achWork, ":");
|
|
strcat(achWork, pszCNonce);
|
|
strcat(achWork, ":");
|
|
strcat(achWork, pszQOP);
|
|
strcat(achWork, ":");
|
|
}
|
|
|
|
strcat(achWork, achHashOut);
|
|
|
|
// Hash it.
|
|
MD5((UCHAR*)achWork, strlen(achWork), achDigest);
|
|
ToHex(achDigest, 16, pszResult);
|
|
|
|
hr = S_OK;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
static const unsigned char g_kPK[] = "Opcutla$14chowx Tcolino!";
|
|
////////////////////////////////////////////////////////////////////
|
|
// Generate a time base nonce for digest
|
|
//
|
|
// IN BYTE *pSrcStr -- Input string should be current time
|
|
// IN long lSrcSize -- Input string size (does not need to be 0 termainate)
|
|
// OUT BYTE *pDestStr -- Return string buffer.
|
|
// IN/OUT long *lDestSize -- Input size of return dest buffer
|
|
// Return string size.
|
|
//
|
|
// Encode buffer size should be >= (((lSrcSize / 3) + 1) * 4) + 1)
|
|
// Decode buffer size should be >= (((lSrcSize / 4) + 1) * 3)
|
|
/////////////////////////////////////////////////////////////////////
|
|
HRESULT WINAPI GenerateNonce(BYTE *pSrcStr, long lSrcSize,
|
|
BYTE *pDestStr, long *lDestSize)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
long len;
|
|
long bufsz;
|
|
BYTE digest[17];
|
|
BYTE buf[125];
|
|
BYTE *tb = (BYTE*)alloca(lSrcSize + 18);
|
|
CBinHex binHexIt;
|
|
BSTR bstrNonce = NULL;
|
|
WCHAR *lpwsz;
|
|
|
|
if(!pSrcStr || !pDestStr || lSrcSize < 1)
|
|
goto exit;
|
|
|
|
memset(digest, 0, sizeof(digest));
|
|
memcpy(tb, pSrcStr, lSrcSize);
|
|
|
|
// use ':' to separate the input str from the pk.
|
|
tb[lSrcSize] = ':';
|
|
memcpy(buf, pSrcStr, lSrcSize);
|
|
memcpy(buf + lSrcSize, g_kPK, sizeof(g_kPK));
|
|
len = lSrcSize + sizeof(g_kPK);
|
|
|
|
//digest form MD5() is always 16 byte long
|
|
MD5(buf, len, digest);
|
|
memcpy(tb + lSrcSize + 1, digest, 16);
|
|
|
|
len = lSrcSize + 17;
|
|
bufsz = sizeof(buf);
|
|
|
|
// encode it all to make nonce - returns an allocated BSTR
|
|
// so make sure at the end of function to free it.
|
|
hr = binHexIt.ToBase64( (LPVOID)tb, len, 0, NULL, &bstrNonce);
|
|
if (FAILED(hr))
|
|
{
|
|
goto exit;
|
|
}
|
|
|
|
bufsz = SysStringLen(bstrNonce);
|
|
if (bufsz >= *lDestSize) // must be room for str + null
|
|
{
|
|
*lDestSize = 0;
|
|
hr = E_INVALIDARG;
|
|
goto exit;
|
|
}
|
|
|
|
*lDestSize = bufsz;
|
|
// convert ascii string in BSTR to char string
|
|
lpwsz = bstrNonce;
|
|
for (;bufsz > 0; bufsz--)
|
|
{
|
|
*pDestStr++ = (BYTE) *lpwsz++; // lpcwsz must be true ASCII) !!
|
|
}
|
|
*pDestStr = 0;
|
|
hr = S_OK;
|
|
|
|
exit:
|
|
SysFreeString(bstrNonce);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// Check if the nonce match
|
|
//
|
|
// IN BYTE *pNonce -- input string(Nonce) to be check
|
|
// IN long lSrcSize -- input string size.
|
|
// IN long lTimeoutWindow -- maximum time the nonce is valid
|
|
// IN long lCurTime -- compare the current session time with nonce.
|
|
//
|
|
// Encode buffer size should be >= (((lSrcSize / 3) + 1) * 4) + 1)
|
|
// Decode buffer size should be >= (((lSrcSize / 4) + 1) * 3)
|
|
//////////////////////////////////////////////////////////////////
|
|
HRESULT WINAPI CheckNonce(BYTE *pNonce, long lSrcSize,
|
|
long lTimeoutWindow, long lCurTime)
|
|
{
|
|
long bufsz = (((lSrcSize / 4) + 1) * 3);
|
|
long len = (((lSrcSize / 3) + 1) * 4) + 2;
|
|
BYTE *buf = (BYTE*)alloca(bufsz);
|
|
BYTE *digest = (BYTE*)alloca(len);
|
|
long dlen = len;
|
|
CBinHex binHexIt;
|
|
HRESULT hr;
|
|
|
|
if(!pNonce || lSrcSize < 1)
|
|
return E_FAIL;
|
|
|
|
pNonce[lSrcSize] = '\0'; //make it 0 terminate
|
|
// decode nonce
|
|
hr = binHexIt.PartFromBase64((LPSTR)pNonce, (LPBYTE)buf, (ULONG *)&bufsz);
|
|
if(FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
buf[bufsz] = '\0';
|
|
|
|
BYTE *end = (BYTE*)strchr((const char*)buf, ':');
|
|
if(!end)
|
|
return PP_E_DIGEST_NONCE_MISSMATCH;
|
|
|
|
len = (long) (end - buf);
|
|
|
|
if(FAILED(GenerateNonce(buf, len, digest, &dlen)))
|
|
return E_FAIL;
|
|
|
|
digest[dlen] = '\0';
|
|
|
|
buf[len] = '\0';
|
|
long ntime = atoi((const char*)buf);
|
|
|
|
// If current time is not passed in, get it
|
|
if (!lCurTime)
|
|
{
|
|
lCurTime = (long) time(NULL);
|
|
}
|
|
if((lCurTime - ntime) > lTimeoutWindow)
|
|
return PP_E_DIGEST_RESPONSE_TIMEOUT;
|
|
|
|
int ret = strcmp((const char*) pNonce, (const char*) digest);
|
|
|
|
return ret ? PP_E_DIGEST_NONCE_MISSMATCH : S_OK;
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// Description:
|
|
//
|
|
// Base64EncodeA This method only provides backward compatibility for
|
|
// existing components.
|
|
//
|
|
// Arguments:
|
|
//
|
|
// pSrc Points to the buffer of bytes to encode.
|
|
//
|
|
// ulSrcSize Number of bytes in pSrc to encode.
|
|
//
|
|
// pszDst Buffer to copy the encoded output into. The length of the buffer
|
|
// must be (((ulSrcSize / 3) + 1) * 4) + 1.
|
|
//
|
|
//Return Value:
|
|
//
|
|
// 0 - Encoding successful.
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
HRESULT WINAPI
|
|
Base64EncodeA(const LPBYTE pSrc,
|
|
ULONG ulSrcSize,
|
|
LPSTR pszDst)
|
|
{
|
|
long lBufLen = -1; //
|
|
Base64_EncodeBytes(pSrc, ulSrcSize, pszDst, &lBufLen);
|
|
pszDst[lBufLen] = '\0';
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// Description:
|
|
//
|
|
// Base64EncodeW This method only provides backward compatibility for
|
|
// existing components.
|
|
//
|
|
// Arguments:
|
|
//
|
|
// pSrc Points to the buffer of bytes to encode.
|
|
//
|
|
// ulSrcSize Number of bytes in pSrc to encode.
|
|
//
|
|
// pszDst Buffer to copy the encoded output into. The length of the buffer
|
|
// must be (((ulSrcSize / 3) + 1) * 4) + 1.
|
|
//
|
|
//Return Value:
|
|
//
|
|
// 0 - Encoding successful.
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
HRESULT WINAPI
|
|
Base64EncodeW(const LPBYTE pSrc,
|
|
ULONG ulSrcSize,
|
|
LPWSTR pszDst)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LONG lBufLen = (((ulSrcSize / 3) + 1) * 4) + 1;
|
|
LPSTR pszABuf = (LPSTR)alloca(lBufLen);
|
|
int nResult;
|
|
|
|
Base64_EncodeBytes(pSrc, ulSrcSize, (char*)pszABuf, &lBufLen);
|
|
pszABuf[lBufLen] = '\0';
|
|
|
|
nResult = MultiByteToWideChar(CP_ACP, 0, pszABuf, lBufLen, pszDst, lBufLen);
|
|
pszDst[nResult] = 0;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|