diff options
author | MÃ¥rten Nordheim <marten.nordheim@qt.io> | 2020-09-09 15:06:28 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2020-09-14 17:12:44 +0000 |
commit | a5a99b9f7e72d5ad677ee2ce86c7c4939f19c456 (patch) | |
tree | dcf1984a876fc3f4f2cb682122e4b6f9c15bdfe8 /src/network | |
parent | 60581e016152e01723a85b53d27c4399100c0488 (diff) |
Schannel: Properly handle request for certificate
Certain servers, like smtp.live.com, will send a request for a
certificate even though they don't require one. In Schannel this
manifests as a warning/info status (SEC_I_INCOMPLETE_CREDENTIALS).
In the cases where it's not needed we should suppress the warning and
try to connect anyway, which is done by calling
InitializeSecurityContext again when we get the status.
Change-Id: I3c48140f2949d8557251a49a2b66946da9395736
Reviewed-by: Joshua GPBeta <studiocghibli@gmail.com>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
(cherry picked from commit 2253d5eca6de707080af9af11bc0dcfdea846fc5)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/ssl/qsslsocket_schannel.cpp | 101 |
1 files changed, 58 insertions, 43 deletions
diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp index e47fda17ab..f91709690a 100644 --- a/src/network/ssl/qsslsocket_schannel.cpp +++ b/src/network/ssl/qsslsocket_schannel.cpp @@ -929,56 +929,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. |