summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/network/ssl/qsslsocket_schannel.cpp101
-rw-r--r--tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp8
2 files changed, 58 insertions, 51 deletions
diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp
index c87d599aff..f0a9f6a027 100644
--- a/src/network/ssl/qsslsocket_schannel.cpp
+++ b/src/network/ssl/qsslsocket_schannel.cpp
@@ -923,56 +923,71 @@ bool QSslSocketBackendPrivate::performHandshake()
if (intermediateBuffer.isEmpty())
return true; // no data, will fail
- SecBuffer inputBuffers[2];
- inputBuffers[0] = createSecBuffer(intermediateBuffer, SECBUFFER_TOKEN);
- inputBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY);
- SecBufferDesc inputBufferDesc{
- SECBUFFER_VERSION,
- ARRAYSIZE(inputBuffers),
- inputBuffers
- };
-
- SecBuffer outBuffers[3];
- outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN);
- outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT);
- outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY);
- auto freeBuffers = qScopeGuard([&outBuffers]() {
+ SecBuffer outBuffers[3] = {};
+ const auto freeOutBuffers = [&outBuffers]() {
for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) {
if (outBuffers[i].pvBuffer)
FreeContextBuffer(outBuffers[i].pvBuffer);
}
- });
- SecBufferDesc outputBufferDesc{
- SECBUFFER_VERSION,
- ARRAYSIZE(outBuffers),
- outBuffers
};
+ const auto outBuffersGuard = qScopeGuard(freeOutBuffers);
+ // For this call to InitializeSecurityContext we may need to call it twice.
+ // In some cases us not having a certificate isn't actually an error, but just a request.
+ // With Schannel, to ignore this warning, we need to call InitializeSecurityContext again
+ // when we get SEC_I_INCOMPLETE_CREDENTIALS! As far as I can tell it's not documented anywhere.
+ // https://stackoverflow.com/a/47479968/2493610
+ SECURITY_STATUS status;
+ short attempts = 2;
+ do {
+ SecBuffer inputBuffers[2];
+ inputBuffers[0] = createSecBuffer(intermediateBuffer, SECBUFFER_TOKEN);
+ inputBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY);
+ SecBufferDesc inputBufferDesc{
+ SECBUFFER_VERSION,
+ ARRAYSIZE(inputBuffers),
+ inputBuffers
+ };
+
+ freeOutBuffers(); // free buffers from any previous attempt
+ outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN);
+ outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT);
+ outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY);
+ SecBufferDesc outputBufferDesc{
+ SECBUFFER_VERSION,
+ ARRAYSIZE(outBuffers),
+ outBuffers
+ };
+
+ ULONG contextReq = getContextRequirements();
+ TimeStamp expiry;
+ status = InitializeSecurityContext(
+ &credentialHandle, // phCredential
+ &contextHandle, // phContext
+ const_reinterpret_cast<SEC_WCHAR *>(targetName().utf16()), // pszTargetName
+ contextReq, // fContextReq
+ 0, // Reserved1
+ 0, // TargetDataRep (unused)
+ &inputBufferDesc, // pInput
+ 0, // Reserved2
+ nullptr, // phNewContext (we already have one)
+ &outputBufferDesc, // pOutput
+ &contextAttributes, // pfContextAttr
+ &expiry // ptsExpiry
+ );
- ULONG contextReq = getContextRequirements();
- TimeStamp expiry;
- auto status = InitializeSecurityContext(&credentialHandle, // phCredential
- &contextHandle, // phContext
- const_reinterpret_cast<SEC_WCHAR *>(targetName().utf16()), // pszTargetName
- contextReq, // fContextReq
- 0, // Reserved1
- 0, // TargetDataRep (unused)
- &inputBufferDesc, // pInput
- 0, // Reserved2
- nullptr, // phNewContext (we already have one)
- &outputBufferDesc, // pOutput
- &contextAttributes, // pfContextAttr
- &expiry // ptsExpiry
- );
+ if (inputBuffers[1].BufferType == SECBUFFER_EXTRA) {
+ // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel
+ // inputBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need
+ // to be stored.
+ retainExtraData(intermediateBuffer, inputBuffers[1]);
+ } else if (status != SEC_E_INCOMPLETE_MESSAGE) {
+ // Clear the buffer if we weren't asked for more data
+ intermediateBuffer.resize(0);
+ }
+
+ --attempts;
+ } while (status == SEC_I_INCOMPLETE_CREDENTIALS && attempts > 0);
- if (inputBuffers[1].BufferType == SECBUFFER_EXTRA) {
- // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel
- // inputBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need to
- // be stored.
- retainExtraData(intermediateBuffer, inputBuffers[1]);
- } else if (status != SEC_E_INCOMPLETE_MESSAGE) {
- // Clear the buffer if we weren't asked for more data
- intermediateBuffer.resize(0);
- }
switch (status) {
case SEC_E_OK:
// Need to transmit a final token in the handshake if 'cbBuffer' is non-zero.
diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp
index 1718b787f5..321eede9c9 100644
--- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp
+++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp
@@ -3391,14 +3391,6 @@ void tst_QSslSocket::verifyClientCertificate()
// check server socket
QVERIFY(server.socket);
-#if QT_CONFIG(schannel)
- // As additional info to the QEXPECT_FAIL below:
- // This is because schannel treats it as an error (client side) if you don't have a certificate
- // when asked for one.
- QEXPECT_FAIL("NoCert:VerifyPeer",
- "The client disconnects first, which causes the event "
- "loop to quit before the server disconnects.", Continue);
-#endif
QCOMPARE(server.socket->state(), expectedState);
QCOMPARE(server.socket->isEncrypted(), works);