0X00090312

SEC_I_CONTINUE_NEEDED 0x00090312 in Windows: Why It Appears and How to Fix It

That 0x90312 code isn't actually a failure. It's Windows telling a TLS or SSPI call to run again with more data. Here's when it bites you and how to handle it.

You'll see SEC_I_CONTINUE_NEEDED (0x00090312) most often when a piece of software is mid-TLS handshake and something upstream decides the conversation is over. Classic trigger: a .NET service does an LDAPS bind to a domain controller, or a WinHTTP client talks to an internal API behind a load balancer, and the app logs the code as if it were a fatal error. The connection drops, the retry storm starts, and someone files a ticket titled "0x00090312 keeps appearing in event log." That's the scenario I get paged for.

What 0x00090312 actually means

The name is the giveaway: SEC_I_CONTINUE_NEEDED. The I stands for informational, not error. SSPI's InitializeSecurityContext and AcceptSecurityContext are state machines. They don't do the whole handshake in one call. Each call consumes input buffers, produces output buffers, and tells you what to do next:

  • SEC_E_OK (0x00000000) — done, context is established.
  • SEC_I_CONTINUE_NEEDED (0x00090312) — more tokens to exchange, call me again.
  • SEC_I_COMPLETE_NEEDED / SEC_I_COMPLETE_AND_CONTINUE — finish the context, sometimes call CompleteAuthToken.
  • SEC_E_INCOMPLETE_MESSAGE (0x80090318) — the buffer you fed me is short. Read more bytes and retry.

So 0x00090312 by itself is normal. It only becomes a problem when code doesn't loop. Common culprits: a custom SspiClient wrapper that treats anything non-zero as fatal, a WinHTTP callback that bails on the first ERROR_WINHTTP_SECURE_FAILURE after the continue, or a shim DLL that swallows the informational code and returns it up the stack as a hard failure. Microsoft's own docs say it plainly: if you get this, feed the output token to the peer and call again.

Where you'll actually run into it

Three real-world spots I've hit in the last year:

  1. Custom SSPI/LDAP code. Someone wrote a Kerberos or NTLM binder against secur32.dll and didn't loop on SEC_I_CONTINUE_NEEDED. Works with NTLM on the LAN, breaks the moment a smart card or PKINIT is involved, because PKINIT needs multiple token round-trips.
  2. .NET SslStream misuse. People call AuthenticateAsClient on a stream that's already been partially negotiated by a proxy, or wrap a NetworkStream without draining pending buffered bytes. You'll get the 0x90312 surfaced through Win32Exception from SspiHandle.
  3. WinHTTP with a TLS-terminating proxy. The proxy returns 407, WinHTTP tries to renegotiate, and the app's WINHTTP_CALLBACK_STATUS_SECURE_FAILURE handler doesn't distinguish "need more data" from "cert is garbage."

The fix

There's no registry key for this one. The fix is in code — yours or the vendor's. Walk it in order.

1. Confirm it's informational, not fatal

Before you touch anything, check whether 0x90312 is the failing code or just the last code logged before a different failure. In .NET, catch the exception and dump the full Win32Exception.NativeErrorCode and the inner one. If the outer error is SEC_E_UNTRUSTED_ROOT (0x80090325) or SEC_E_WRONG_PRINCIPAL (0x80090322), the 0x90312 is a red herring. Trace with Wireshark on 443 and look for the last TLS alert before the disconnect. That tells you the real story.

2. Fix the calling code to loop

If it's your code, the pattern is straightforward. Don't return on non-zero unless it's a real error:

SECURITY_STATUS status;
CtxtHandle hCtx = {0};
SecBuffer outBuf = {0};
SecBufferDesc outDesc = { SECBUFFER_VERSION, 1, &outBuf };
PBYTE pIn = NULL; ULONG cbIn = 0;

for (;;) {
    status = InitializeSecurityContext(
        &cred, hCtx.dwLower ? &hCtx : NULL,
        targetSpn, ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM,
        0, 0, NULL, 0, &hCtx, &outDesc, &attrs, NULL);

    if (status == SEC_I_CONTINUE_NEEDED) {
        SendTokenToPeer(outBuf.pvBuffer, outBuf.cbBuffer);
        SecFree(outBuf.pvBuffer);
        RecvTokenFromPeer(&pIn, &cbIn);   // read the peer's token
        continue;
    }
    if (status == SEC_E_INCOMPLETE_MESSAGE) {
        ReadMoreBytes();
        continue;
    }
    if (status == SEC_E_OK) break;
    // anything else is a real failure
    return status;
}

Two things bite people here: you must free the output buffer if you passed ISC_REQ_ALLOCATE_MEMORY, and you must pass the correct input buffer on the next call. A null input buffer on the second iteration is the single most common bug I've seen.

3. Fix .NET SslStream setups

If you're using SslStream and seeing this, stop wrapping streams manually. Use the async overloads and let the framework own the state machine:

var ssl = new SslStream(tcp.GetStream(), false,
    (s, cert, chain, errors) => errors == SslPolicyErrors.None);
await ssl.AuthenticateAsClientAsync(targetHost, null,
    SslProtocols.Tls12 | SslProtocols.Tls13, true);

Also make sure you're on .NET 4.8 or .NET 6+. A batch of older 4.6.x builds mishandled SEC_I_COMPLETE_NEEDED with smart cards. If you're pinned to 4.6.x for some legacy reason, update — you'll save yourself a week.

4. Check for an intercepting proxy or TLS inspection

If neither your code nor the framework is obviously at fault, something in the path is doing TLS renegotiation or MITM. Common sources:

  • Zscaler, Netskope, Palo Alto decryption policy, or a FortiGate doing SSL inspection.
  • An internal load balancer that renegotiates client certs and doesn't pass the intermediate token cleanly.
  • A shim DLL from a third-party VPN or DLP agent that hooks secur32!InitializeSecurityContextW.

Test from a machine on the same subnet that isn't behind the proxy. If the error vanishes, it's a path problem, not your app.

If it still fails

Work down this list before opening a case with Microsoft — they'll ask for all of it anyway.

  1. Grab a network trace with Wireshark on 443. Filter tls.handshake and look at the last alert. If you see handshake_failure (40) right after a renegotiation, it's the proxy.
  2. Turn on Schannel logging: HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\EventLogging = 1, then check System log for event 36871/36874/36888. Those give you the underlying crypto reason.
  3. Update the root CA store. An outdated Root store is a silent killer on Server 2016 boxes that haven't patched since 2021.
  4. Test with curl.exe -v https://target over the same network path. Modern Windows ships a real curl. If curl succeeds and your app fails, the bug is in your code or your TLS stack.
  5. Check TLS versions. If the peer only offers TLS 1.3 and you're on Server 2012 R2, you'll get a cascade that ends in odd SSPI codes. Enable TLS 1.2 client-side via HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client and set Enabled = 1, DisabledByDefault = 0.

The short version: 0x00090312 is almost never the real problem. It's the last handshake step before something else went wrong. Find that something else and this code disappears on its own.

Related Errors in Windows Errors
0X00000191 Fix ERROR_THREAD_MODE_NOT_BACKGROUND (0x191) in Windows 10/11 0XC019004F STATUS_RESOURCEMANAGER_NOT_FOUND (0XC019004F) — KTM fix 0XC01E0350 Fix STATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS 0xC01E0350 0X0000208E Active Directory: Fix ERROR_DS_ALIASED_OBJ_MISSING (0x208E)

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.