From fe59436763b407418e66bf47aa7f1fb34470d093 Mon Sep 17 00:00:00 2001
From: Maksim Kita <kitaetoya@gmail.com>
Date: Mon, 30 May 2022 04:52:39 +0200
Subject: [PATCH] HTTPDigestCredentials added support for RFC7616 algorithms
 (#3026)

---
 Foundation/include/Poco/SHA2Engine.h         |  10 +-
 Foundation/src/SHA2Engine.cpp                |  60 +++++++--
 Foundation/testsuite/src/SHA2EngineTest.cpp  |  56 +++++++-
 Foundation/testsuite/src/SHA2EngineTest.h    |   2 +
 Net/include/Poco/Net/HTTPDigestCredentials.h |  17 ++-
 Net/src/HTTPDigestCredentials.cpp            | 131 +++++++++++++++++--
 Net/testsuite/src/HTTPCredentialsTest.cpp    |  79 +++++++++++
 Net/testsuite/src/HTTPCredentialsTest.h      |   3 +
 8 files changed, 331 insertions(+), 27 deletions(-)

diff --git a/Foundation/include/Poco/SHA2Engine.h b/Foundation/include/Poco/SHA2Engine.h
index 67417100c..c1de5cd50 100644
--- a/Foundation/include/Poco/SHA2Engine.h
+++ b/Foundation/include/Poco/SHA2Engine.h
@@ -38,10 +38,12 @@ class Foundation_API SHA2Engine: public DigestEngine
 public:
 	enum ALGORITHM
 	{
-		SHA_224 = 224,
-		SHA_256 = 256,
-		SHA_384 = 384,
-		SHA_512 = 512
+		SHA_224 = 1,
+		SHA_256,
+		SHA_384,
+		SHA_512,
+		SHA_512_224,
+		SHA_512_256
 	};
 
 	SHA2Engine(ALGORITHM algorithm = SHA_256);
diff --git a/Foundation/src/SHA2Engine.cpp b/Foundation/src/SHA2Engine.cpp
index b09b30a79..cc3e84031 100644
--- a/Foundation/src/SHA2Engine.cpp
+++ b/Foundation/src/SHA2Engine.cpp
@@ -41,7 +41,6 @@ typedef struct
 		Poco::UInt64 state64[8];
 	} state;
 
-	SHA2Engine::ALGORITHM size;
 	unsigned char buffer[128];
 } HASHCONTEXT;
 
@@ -274,7 +273,7 @@ void SHA2Engine::updateImpl(const void* buffer_, std::size_t count)
 	Poco::UInt32 left = 0;
 	HASHCONTEXT* pContext = (HASHCONTEXT*)_context;
 	unsigned char* data = (unsigned char*)buffer_;
-	if (pContext->size > SHA_256)
+	if (_algorithm > SHA_256)
 	{
 		left = (Poco::UInt32)(pContext->total.total64[0] & 0x7F);
 		size_t fill = 128 - left;
@@ -323,7 +322,31 @@ void SHA2Engine::updateImpl(const void* buffer_, std::size_t count)
 
 std::size_t SHA2Engine::digestLength() const
 {
-	return (size_t)((int)_algorithm / 8);
+	size_t result = 0;
+
+	switch (_algorithm)
+	{
+    case SHA_224:
+		result = 224;
+		break;
+	case SHA_256:
+		result = 256;
+		break;
+	case SHA_384:
+		result = 384;
+		break;
+	case SHA_512:
+		result = 512;
+		break;
+	case SHA_512_224:
+		result = 224;
+		break;
+	case SHA_512_256:
+		result = 256;
+		break;
+	}
+
+	return result / 8;
 }
 
 
@@ -332,7 +355,6 @@ void SHA2Engine::reset()
 	if (_context != NULL) free(_context);
 	_context = calloc(1, sizeof(HASHCONTEXT));
 	HASHCONTEXT* pContext = (HASHCONTEXT*)_context;
-	pContext->size = _algorithm;
 	if (_algorithm == SHA_224)
 	{
 		pContext->state.state32[0] = 0xC1059ED8;
@@ -366,7 +388,7 @@ void SHA2Engine::reset()
 		pContext->state.state64[6] = UL64(0xDB0C2E0D64F98FA7);
 		pContext->state.state64[7] = UL64(0x47B5481DBEFA4FA4);
 	}
-	else
+	else if (_algorithm == SHA_512)
 	{
 		pContext->state.state64[0] = UL64(0x6A09E667F3BCC908);
 		pContext->state.state64[1] = UL64(0xBB67AE8584CAA73B);
@@ -377,6 +399,27 @@ void SHA2Engine::reset()
 		pContext->state.state64[6] = UL64(0x1F83D9ABFB41BD6B);
 		pContext->state.state64[7] = UL64(0x5BE0CD19137E2179);
 	}
+	else if (_algorithm == SHA_512_224)
+	{
+		pContext->state.state64[0] = UL64(0x8C3D37C819544DA2);
+		pContext->state.state64[1] = UL64(0x73E1996689DCD4D6);
+		pContext->state.state64[2] = UL64(0x1DFAB7AE32FF9C82);
+		pContext->state.state64[3] = UL64(0x679DD514582F9FCF);
+		pContext->state.state64[4] = UL64(0x0F6D2B697BD44DA8);
+		pContext->state.state64[5] = UL64(0x77E36F7304C48942);
+		pContext->state.state64[6] = UL64(0x3F9D85A86A1D36C8);
+		pContext->state.state64[7] = UL64(0x1112E6AD91D692A1);
+	} else
+	{
+		pContext->state.state64[0] = UL64(0x22312194FC2BF72C);
+		pContext->state.state64[1] = UL64(0x9F555FA3C84C64C2);
+		pContext->state.state64[2] = UL64(0x2393B86B6F53B151);
+		pContext->state.state64[3] = UL64(0x963877195940EABD);
+		pContext->state.state64[4] = UL64(0x96283EE2A88EFFE3);
+		pContext->state.state64[5] = UL64(0xBE5E1E2553863992);
+		pContext->state.state64[6] = UL64(0x2B0199FC2C85B8AA);
+		pContext->state.state64[7] = UL64(0x0EB72DDC81C52CA2);
+	}
 }
 
 
@@ -388,7 +431,8 @@ const DigestEngine::Digest& SHA2Engine::digest()
 	size_t last, padn;
 	unsigned char hash[64];
 	memset(hash, 0, 64);
-	if (pContext->size > SHA_256)
+
+	if (_algorithm > SHA_256)
 	{
 		unsigned char msglen[16];
 		Poco::UInt64 high = (pContext->total.total64[0] >> 61) | (pContext->total.total64[1] << 3);
@@ -405,7 +449,7 @@ const DigestEngine::Digest& SHA2Engine::digest()
 		PUT_UINT64(pContext->state.state64[3], hash, 24);
 		PUT_UINT64(pContext->state.state64[4], hash, 32);
 		PUT_UINT64(pContext->state.state64[5], hash, 40);
-		if (pContext->size > SHA_384)
+		if (_algorithm > SHA_384)
 		{
 			PUT_UINT64(pContext->state.state64[6], hash, 48);
 			PUT_UINT64(pContext->state.state64[7], hash, 56);
@@ -429,7 +473,7 @@ const DigestEngine::Digest& SHA2Engine::digest()
 		PUT_UINT32(pContext->state.state32[4], hash, 16);
 		PUT_UINT32(pContext->state.state32[5], hash, 20);
 		PUT_UINT32(pContext->state.state32[6], hash, 24);
-		if (pContext->size > SHA_224) PUT_UINT32(pContext->state.state32[7], hash, 28);
+		if (_algorithm > SHA_224) PUT_UINT32(pContext->state.state32[7], hash, 28);
 	}
 	_digest.insert(_digest.begin(), hash, hash + digestLength());
 	reset();
diff --git a/Foundation/testsuite/src/SHA2EngineTest.cpp b/Foundation/testsuite/src/SHA2EngineTest.cpp
index 90ec4a5e7..0ba0c64d3 100644
--- a/Foundation/testsuite/src/SHA2EngineTest.cpp
+++ b/Foundation/testsuite/src/SHA2EngineTest.cpp
@@ -36,6 +36,9 @@ void SHA2EngineTest::testSHA224()
 {
 	SHA2Engine engine(SHA2Engine::SHA_224);
 
+	engine.update("");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f");
+
 	engine.update("abc");
 	assertTrue (DigestEngine::digestToHex(engine.digest()) == "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7");
 
@@ -55,6 +58,9 @@ void SHA2EngineTest::testSHA256()
 {
 	SHA2Engine engine(SHA2Engine::SHA_256);
 
+	engine.update("");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+
 	engine.update("abc");
 	assertTrue (DigestEngine::digestToHex(engine.digest()) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
 
@@ -74,6 +80,9 @@ void SHA2EngineTest::testSHA384()
 {
 	SHA2Engine engine(SHA2Engine::SHA_384);
 
+	engine.update("");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b");
+
 	engine.update("abc");
 	assertTrue (DigestEngine::digestToHex(engine.digest()) == "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7");
 
@@ -93,6 +102,9 @@ void SHA2EngineTest::testSHA512()
 {
 	SHA2Engine engine(SHA2Engine::SHA_512);
 
+	engine.update("");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
+
 	engine.update("abc");
 	assertTrue (DigestEngine::digestToHex(engine.digest()) == "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f");
 
@@ -107,6 +119,47 @@ void SHA2EngineTest::testSHA512()
 	assertTrue (DigestEngine::digestToHex(engine.digest()) == "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b");
 }
 
+void SHA2EngineTest::testSHA512_224()
+{
+	SHA2Engine engine(SHA2Engine::SHA_512_224);
+
+	engine.update("");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4");
+
+	engine.update("abc");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa");
+
+	engine.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "e5302d6d54bb242275d1e7622d68df6eb02dedd13f564c13dbda2174");
+
+	engine.update("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "23fec5bb94d60b23308192640b0c453335d664734fe40e7268674af9");
+
+	for (int i = 0; i < 1000000; ++i)
+		engine.update('a');
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "37ab331d76f0d36de422bd0edeb22a28accd487b7a8453ae965dd287");
+}
+
+void SHA2EngineTest::testSHA512_256()
+{
+	SHA2Engine engine(SHA2Engine::SHA_512_256);
+
+	engine.update("");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a");
+
+	engine.update("abc");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23");
+
+	engine.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "bde8e1f9f19bb9fd3406c90ec6bc47bd36d8ada9f11880dbc8a22a7078b6a461");
+
+	engine.update("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "3928e184fb8690f840da3988121d31be65cb9d3ef83ee6146feac861e19b563a");
+
+	for (int i = 0; i < 1000000; ++i)
+		engine.update('a');
+	assertTrue (DigestEngine::digestToHex(engine.digest()) == "9a59a052930187a97038cae692f30708aa6491923ef5194394dc68d56c74fb21");
+}
 
 void SHA2EngineTest::setUp()
 {
@@ -126,6 +179,7 @@ CppUnit::Test* SHA2EngineTest::suite()
 	CppUnit_addTest(pSuite, SHA2EngineTest, testSHA256);
 	CppUnit_addTest(pSuite, SHA2EngineTest, testSHA384);
 	CppUnit_addTest(pSuite, SHA2EngineTest, testSHA512);
-
+	CppUnit_addTest(pSuite, SHA2EngineTest, testSHA512_224);
+	CppUnit_addTest(pSuite, SHA2EngineTest, testSHA512_256);
 	return pSuite;
 }
diff --git a/Foundation/testsuite/src/SHA2EngineTest.h b/Foundation/testsuite/src/SHA2EngineTest.h
index b1a8bf326..ffd795878 100644
--- a/Foundation/testsuite/src/SHA2EngineTest.h
+++ b/Foundation/testsuite/src/SHA2EngineTest.h
@@ -28,6 +28,8 @@ public:
 	void testSHA256();
 	void testSHA384();
 	void testSHA512();
+	void testSHA512_224();
+	void testSHA512_256();
 
 	void setUp();
 	void tearDown();
diff --git a/Net/include/Poco/Net/HTTPDigestCredentials.h b/Net/include/Poco/Net/HTTPDigestCredentials.h
index c321c9fc8..b8c5af438 100644
--- a/Net/include/Poco/Net/HTTPDigestCredentials.h
+++ b/Net/include/Poco/Net/HTTPDigestCredentials.h
@@ -120,6 +120,9 @@ public:
 		/// and HTTPAuthenticationParams by recomputing the response and comparing
 		/// it with what's in the request.
 
+	bool isAlgorithmSupported(const std::string& algorithm) const;
+		/// Check if digest algorithm is supported
+
 	static std::string createNonce();
 		/// Creates a random nonce string.
 
@@ -133,7 +136,19 @@ private:
 	void updateAuthParams(const HTTPRequest& request);
 	int updateNonceCounter(const std::string& nonce);
 
-	static const std::string DEFAULT_ALGORITHM;
+	class DigestEngineProvider;
+	
+	static const std::string MD_5_ALGORITHM;
+	static const std::string MD_5_SESS_ALGORITHM;
+	static const std::string SHA_ALGORITHM;
+	static const std::string SHA_SESS_ALGORITHM;
+	static const std::string SHA_256_ALGORITHM;
+	static const std::string SHA_256_SESS_ALGORITHM;
+	static const std::string SHA_512_256_ALGORITHM;
+	static const std::string SHA_512_256_SESS_ALGORITHM;
+	static const std::string SHA_512_ALGORITHM;
+	static const std::string SHA_512_SESS_ALGORITHM;
+	static const std::vector<std::string> SUPPORTED_ALGORITHMS;
 	static const std::string DEFAULT_QOP;
 	static const std::string NONCE_PARAM;
 	static const std::string REALM_PARAM;
diff --git a/Net/src/HTTPDigestCredentials.cpp b/Net/src/HTTPDigestCredentials.cpp
index ac810a6a2..b8543c56e 100644
--- a/Net/src/HTTPDigestCredentials.cpp
+++ b/Net/src/HTTPDigestCredentials.cpp
@@ -18,12 +18,15 @@
 #include "Poco/DateTimeFormatter.h"
 #include "Poco/Exception.h"
 #include "Poco/MD5Engine.h"
+#include "Poco/SHA1Engine.h"
+#include "Poco/SHA2Engine.h"
 #include "Poco/Net/HTTPDigestCredentials.h"
 #include "Poco/Net/HTTPRequest.h"
 #include "Poco/Net/HTTPResponse.h"
 #include "Poco/NumberFormatter.h"
 #include "Poco/StringTokenizer.h"
 
+#include <iostream>
 
 namespace
 {
@@ -68,7 +71,28 @@ namespace Net {
 
 
 const std::string HTTPDigestCredentials::SCHEME = "Digest";
-const std::string HTTPDigestCredentials::DEFAULT_ALGORITHM("MD5");
+const std::vector<std::string> HTTPDigestCredentials::SUPPORTED_ALGORITHMS = {
+	"MD5",
+	"MD5-sess",
+	"SHA",
+	"SHA-sess",
+	"SHA-256",
+	"SHA-256-sess",
+	"SHA-512-256",
+	"SHA-512-256-sess",
+	"SHA-512",
+	"SHA-512-sess"
+};
+const std::string HTTPDigestCredentials::MD_5_ALGORITHM = "MD5";
+const std::string HTTPDigestCredentials::MD_5_SESS_ALGORITHM = "MD5-sess";
+const std::string HTTPDigestCredentials::SHA_ALGORITHM = "SHA";
+const std::string HTTPDigestCredentials::SHA_SESS_ALGORITHM = "SHA-sess";
+const std::string HTTPDigestCredentials::SHA_256_ALGORITHM = "SHA-256";
+const std::string HTTPDigestCredentials::SHA_256_SESS_ALGORITHM = "SHA-256-sess";
+const std::string HTTPDigestCredentials::SHA_512_256_ALGORITHM = "SHA-512-256";
+const std::string HTTPDigestCredentials::SHA_512_256_SESS_ALGORITHM = "SHA-512-256-sess";
+const std::string HTTPDigestCredentials::SHA_512_ALGORITHM = "SHA-512";
+const std::string HTTPDigestCredentials::SHA_512_SESS_ALGORITHM = "SHA-512-sess";
 const std::string HTTPDigestCredentials::DEFAULT_QOP("");
 const std::string HTTPDigestCredentials::NONCE_PARAM("nonce");
 const std::string HTTPDigestCredentials::REALM_PARAM("realm");
@@ -84,6 +108,44 @@ const std::string HTTPDigestCredentials::NC_PARAM("nc");
 int HTTPDigestCredentials::_nonceCounter(0);
 Poco::FastMutex HTTPDigestCredentials::_nonceMutex;
 
+class HTTPDigestCredentials::DigestEngineProvider {
+public:
+	DigestEngineProvider(std::string algorithm): _algorithm(algorithm) {
+		_isSessionAlgorithm = _algorithm.find("sess") != std::string::npos;
+	}
+
+	DigestEngine& engine() {
+		if (icompare(_algorithm, SHA_ALGORITHM) == 0 || icompare(_algorithm, SHA_SESS_ALGORITHM) == 0)
+		{
+			return _sha1Engine;
+		}
+		if (icompare(_algorithm, SHA_256_ALGORITHM) == 0 || icompare(_algorithm, SHA_256_SESS_ALGORITHM) == 0)
+		{
+			return _sha256Engine;
+		} else if (icompare(_algorithm, SHA_512_256_ALGORITHM) == 0 || icompare(_algorithm, SHA_512_256_SESS_ALGORITHM) == 0)
+		{
+			return _sha512_256Engine;
+		} else if (icompare(_algorithm, SHA_512_ALGORITHM) == 0 || icompare(_algorithm, SHA_512_SESS_ALGORITHM) == 0)
+		{
+			return _sha512;
+		}
+		else {
+			return _md5Engine;
+		}
+	}
+
+	bool isSessionAlgorithm() {
+		return _isSessionAlgorithm;
+	}
+private:
+	std::string _algorithm;
+	SHA1Engine _sha1Engine;
+	MD5Engine _md5Engine;
+	SHA2Engine _sha256Engine { SHA2Engine::ALGORITHM::SHA_256 };
+	SHA2Engine _sha512_256Engine { SHA2Engine::ALGORITHM::SHA_512_256 };
+	SHA2Engine _sha512 { SHA2Engine::ALGORITHM::SHA_512 };
+	bool _isSessionAlgorithm;
+};
 
 HTTPDigestCredentials::HTTPDigestCredentials()
 {
@@ -191,11 +253,6 @@ void HTTPDigestCredentials::createAuthParams(const HTTPRequest& request, const H
 	if (!responseAuthParams.has(NONCE_PARAM) || !responseAuthParams.has(REALM_PARAM))
 		throw InvalidArgumentException("Invalid HTTP authentication parameters");
 
-	const std::string& algorithm = responseAuthParams.get(ALGORITHM_PARAM, DEFAULT_ALGORITHM);
-
-	if (icompare(algorithm, DEFAULT_ALGORITHM) != 0)
-		throw NotImplementedException("Unsupported digest algorithm", algorithm);
-
 	const std::string& nonce = responseAuthParams.get(NONCE_PARAM);
 	const std::string& qop = responseAuthParams.get(QOP_PARAM, DEFAULT_QOP);
 	const std::string& realm = responseAuthParams.getRealm();
@@ -208,6 +265,10 @@ void HTTPDigestCredentials::createAuthParams(const HTTPRequest& request, const H
 	{
 		_requestAuthParams.set(OPAQUE_PARAM, responseAuthParams.get(OPAQUE_PARAM));
 	}
+	if (responseAuthParams.has(ALGORITHM_PARAM))
+	{
+		_requestAuthParams.set(ALGORITHM_PARAM, responseAuthParams.get(ALGORITHM_PARAM));
+	}
 
 	if (qop.empty())
 	{
@@ -233,10 +294,8 @@ void HTTPDigestCredentials::createAuthParams(const HTTPRequest& request, const H
 	}
 }
 
-
 void HTTPDigestCredentials::updateAuthParams(const HTTPRequest& request)
 {
-	MD5Engine engine;
 	const std::string qop = _requestAuthParams.get(QOP_PARAM, DEFAULT_QOP);
 	const std::string realm = _requestAuthParams.getRealm();
 	const std::string nonce = _requestAuthParams.get(NONCE_PARAM);
@@ -245,6 +304,11 @@ void HTTPDigestCredentials::updateAuthParams(const HTTPRequest& request)
 
 	if (qop.empty())
 	{
+		/// Assume that https://tools.ietf.org/html/rfc7616 does not supported 
+		/// and still using https://tools.ietf.org/html/rfc2069#section-2.4	
+
+		MD5Engine engine;
+
 		const std::string ha1 = digest(engine, _username, realm, _password);
 		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
 
@@ -252,13 +316,28 @@ void HTTPDigestCredentials::updateAuthParams(const HTTPRequest& request)
 	}
 	else if (icompare(qop, AUTH_PARAM) == 0)
 	{
-		const std::string cnonce = _requestAuthParams.get(CNONCE_PARAM);
+		const std::string algorithm = _requestAuthParams.get(ALGORITHM_PARAM, MD_5_ALGORITHM);
 
-		const std::string ha1 = digest(engine, _username, realm, _password);
-		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
+		if (!isAlgorithmSupported(algorithm)) {
+			throw NotImplementedException("Unsupported digest algorithm", algorithm);
+		}
+
+		const std::string cnonce = _requestAuthParams.get(CNONCE_PARAM);
 		const std::string nc = formatNonceCounter(updateNonceCounter(nonce));
 
+		DigestEngineProvider engineProvider(algorithm);
+		DigestEngine &engine = engineProvider.engine();
+
+		std::string ha1 = digest(engine, _username, realm, _password);
+
+		if (engineProvider.isSessionAlgorithm()) {
+			ha1 = digest(engine, ha1, nonce, cnonce);
+		}
+
+		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());  
+		
 		_requestAuthParams.set(NC_PARAM, nc);
+		_requestAuthParams.set(CNONCE_PARAM, cnonce);
 		_requestAuthParams.set(RESPONSE_PARAM, digest(engine, ha1, nonce, nc, cnonce, qop, ha2));
 	}
 }
@@ -277,18 +356,34 @@ bool HTTPDigestCredentials::verifyAuthParams(const HTTPRequest& request, const H
 	const std::string& realm = params.getRealm();
 	const std::string& qop   = params.get(QOP_PARAM, DEFAULT_QOP);
 	std::string response;
-	MD5Engine engine;
 	if (qop.empty())
 	{
+		MD5Engine engine;
+
 		const std::string ha1 = digest(engine, _username, realm, _password);
 		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
 		response = digest(engine, ha1, nonce, ha2);
 	}
 	else if (icompare(qop, AUTH_PARAM) == 0)
 	{
+		const std::string& algorithm = params.get(ALGORITHM_PARAM, MD_5_ALGORITHM);
+		
+		if (!isAlgorithmSupported(algorithm)) {
+			throw NotImplementedException("Unsupported digest algorithm", algorithm);
+		}
+
+		DigestEngineProvider engineProvider(algorithm);
+		DigestEngine& engine = engineProvider.engine();
+
 		const std::string& cnonce = params.get(CNONCE_PARAM);
 		const std::string& nc = params.get(NC_PARAM);
-		const std::string ha1 = digest(engine, _username, realm, _password);
+
+		std::string ha1 = digest(engine, _username, realm, _password);
+
+		if (engineProvider.isSessionAlgorithm()) {
+			ha1 = digest(engine, ha1, nonce, cnonce);
+		}
+
 		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
 		response = digest(engine, ha1, nonce, nc, cnonce, qop, ha2);
 	}
@@ -309,5 +404,15 @@ int HTTPDigestCredentials::updateNonceCounter(const std::string& nonce)
 	return iter->second;
 }
 
+bool HTTPDigestCredentials::isAlgorithmSupported(const std::string& algorithm) const
+{
+	bool isAlgorithmSupported = std::find_if(std::begin(SUPPORTED_ALGORITHMS), 
+											std::end(SUPPORTED_ALGORITHMS),
+											[&algorithm](const std::string& supportedAlgorithm) {
+		return icompare(algorithm, supportedAlgorithm) == 0;
+	}) != std::end(SUPPORTED_ALGORITHMS);
+
+	return isAlgorithmSupported;
+}
 
 } } // namespace Poco::Net
diff --git a/Net/testsuite/src/HTTPCredentialsTest.cpp b/Net/testsuite/src/HTTPCredentialsTest.cpp
index f9738b768..d0c0dce27 100644
--- a/Net/testsuite/src/HTTPCredentialsTest.cpp
+++ b/Net/testsuite/src/HTTPCredentialsTest.cpp
@@ -214,6 +214,45 @@ void HTTPCredentialsTest::testDigestCredentialsQoP()
 	assertTrue (params.size() == 9);
 }
 
+void HTTPCredentialsTest::testDigestCredentialsQoPSHA256()
+{
+	HTTPDigestCredentials creds("user", "s3cr3t");
+	HTTPRequest request(HTTPRequest::HTTP_GET, "/digest/");
+	HTTPResponse response;
+	response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth,auth-int\", algorithm=SHA-256");
+	creds.authenticate(request, response);
+	
+	HTTPAuthenticationParams params(request);
+	assertTrue (params["nonce"] == "212573bb90170538efad012978ab811f%lu");
+	assertTrue (params["realm"] == "TestDigest");
+	assertTrue (params["response"] != "40e4889cfbd0e561f71e3107a2863bc4");
+	assertTrue (params["uri"] == "/digest/");
+	assertTrue (params["username"] == "user");
+	assertTrue (params["opaque"] == "opaque");
+	assertTrue (params["cnonce"] != "");
+	assertTrue (params["nc"] == "00000001");
+	assertTrue (params["qop"] == "auth");
+	assertTrue (params["algorithm"] == "SHA-256");
+	assertTrue (params.size() == 10);
+
+	std::string cnonce = params["cnonce"];
+	std::string aresp = params["response"];
+
+	params.clear();
+
+	creds.updateAuthInfo(request);
+	params.fromRequest(request);
+	assertTrue (params["nonce"] == "212573bb90170538efad012978ab811f%lu");
+	assertTrue (params["realm"] == "TestDigest");
+	assertTrue (params["response"] != aresp);
+	assertTrue (params["uri"] == "/digest/");
+	assertTrue (params["username"] == "user");
+	assertTrue (params["opaque"] == "opaque");
+	assertTrue (params["cnonce"] == cnonce);
+	assertTrue (params["nc"] == "00000002");
+	assertTrue (params["qop"] == "auth");
+	assertTrue (params.size() == 10);
+}
 
 void HTTPCredentialsTest::testCredentialsBasic()
 {
@@ -311,6 +350,43 @@ void HTTPCredentialsTest::testVerifyAuthInfoQoP()
 	assertTrue (!creds.verifyAuthInfo(request));
 }
 
+void HTTPCredentialsTest::testVerifyAuthInfoQoPSHA256() {
+	HTTPDigestCredentials sha256Creds("user", "s3cr3t");
+	HTTPRequest sha256Request(HTTPRequest::HTTP_GET, "/digest/");
+	HTTPResponse sha256Response;
+	sha256Response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth,auth-int\", algorithm=SHA-256");
+	sha256Creds.authenticate(sha256Request, sha256Response);
+	assertTrue (sha256Creds.verifyAuthInfo(sha256Request));
+
+	sha256Request.set("Authorization", "Digest cnonce=\"f9c80ffd1c3bc4ee47ed92b704ba75a4\", nc=00000001, nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth\", realm=\"TestDigest\", response=\"ff0e90b9aa019120ea0ed6e23ce95d9a\", uri=\"/digest/\", username=\"user\", algorithm=SHA-256");
+	assertTrue (!sha256Creds.verifyAuthInfo(sha256Request));
+
+	HTTPDigestCredentials sha256SessCreds("user", "s3cr3t");
+	HTTPRequest sha256SessRequest(HTTPRequest::HTTP_GET, "/digest/");
+	HTTPResponse sha256SessResponse;
+	sha256SessResponse.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth,auth-int\", algorithm=SHA-256-sess");
+	sha256SessCreds.authenticate(sha256SessRequest, sha256SessResponse);
+	assertTrue (sha256SessCreds.verifyAuthInfo(sha256SessRequest));
+
+	sha256SessRequest.set("Authorization", "Digest cnonce=\"f9c80ffd1c3bc4ee47ed92b704ba75a4\", nc=00000001, nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth\", realm=\"TestDigest\", response=\"ff0e90b9aa019120ea0ed6e23ce95d9a\", uri=\"/digest/\", username=\"user\", algorithm=SHA-256-sess");
+	assertTrue (!sha256SessCreds.verifyAuthInfo(sha256SessRequest));
+}
+
+void HTTPCredentialsTest::testIsAlgorithmSupported() {
+	HTTPDigestCredentials sha256Creds("user", "s3cr3t");
+
+	assertTrue (sha256Creds.isAlgorithmSupported("MD5"));
+	assertTrue (sha256Creds.isAlgorithmSupported("MD5-sess"));
+	assertTrue (sha256Creds.isAlgorithmSupported("SHA"));
+	assertTrue (sha256Creds.isAlgorithmSupported("SHA-sess"));
+	assertTrue (sha256Creds.isAlgorithmSupported("SHA-256"));
+	assertTrue (sha256Creds.isAlgorithmSupported("SHA-256-sess"));
+	assertTrue (sha256Creds.isAlgorithmSupported("SHA-512-256"));
+	assertTrue (sha256Creds.isAlgorithmSupported("SHA-512-256-sess"));
+	assertTrue (sha256Creds.isAlgorithmSupported("SHA-512"));
+	assertTrue (sha256Creds.isAlgorithmSupported("SHA-512-sess"));
+	assertFalse (sha256Creds.isAlgorithmSupported("random_algorithm"));
+}
 
 void HTTPCredentialsTest::setUp()
 {
@@ -333,6 +409,7 @@ CppUnit::Test* HTTPCredentialsTest::suite()
 	CppUnit_addTest(pSuite, HTTPCredentialsTest, testAuthenticationParamsMultipleHeaders);
 	CppUnit_addTest(pSuite, HTTPCredentialsTest, testDigestCredentials);
 	CppUnit_addTest(pSuite, HTTPCredentialsTest, testDigestCredentialsQoP);
+	CppUnit_addTest(pSuite, HTTPCredentialsTest, testDigestCredentialsQoPSHA256);
 	CppUnit_addTest(pSuite, HTTPCredentialsTest, testCredentialsBasic);
 	CppUnit_addTest(pSuite, HTTPCredentialsTest, testProxyCredentialsBasic);
 	CppUnit_addTest(pSuite, HTTPCredentialsTest, testCredentialsDigest);
@@ -341,6 +418,8 @@ CppUnit::Test* HTTPCredentialsTest::suite()
 	CppUnit_addTest(pSuite, HTTPCredentialsTest, testExtractCredentials);
 	CppUnit_addTest(pSuite, HTTPCredentialsTest, testVerifyAuthInfo);
 	CppUnit_addTest(pSuite, HTTPCredentialsTest, testVerifyAuthInfoQoP);
+	CppUnit_addTest(pSuite, HTTPCredentialsTest, testVerifyAuthInfoQoPSHA256);
+	CppUnit_addTest(pSuite, HTTPCredentialsTest, testIsAlgorithmSupported);
 
 	return pSuite;
 }
diff --git a/Net/testsuite/src/HTTPCredentialsTest.h b/Net/testsuite/src/HTTPCredentialsTest.h
index 56c9cb0d5..ba8a0f07a 100644
--- a/Net/testsuite/src/HTTPCredentialsTest.h
+++ b/Net/testsuite/src/HTTPCredentialsTest.h
@@ -31,6 +31,7 @@ public:
 	void testAuthenticationParamsMultipleHeaders();
 	void testDigestCredentials();
 	void testDigestCredentialsQoP();
+	void testDigestCredentialsQoPSHA256();
 	void testCredentialsBasic();
 	void testProxyCredentialsBasic();
 	void testCredentialsDigest();
@@ -39,6 +40,8 @@ public:
 	void testExtractCredentials();
 	void testVerifyAuthInfo();
 	void testVerifyAuthInfoQoP();
+	void testVerifyAuthInfoQoPSHA256();
+	void testIsAlgorithmSupported();
 
 	void setUp();
 	void tearDown();