jstedfast/MailKit · error · SaslException

IncompleteChallenge

IncompleteChallenge

Error message

Challenge did not contain a salt.

What it means

When parsing the SCRAM server-first message, MailKit requires the 's' (salt, base64) attribute per RFC 5802. If the parsed challenge tokens contain no 's' entry, SaslException with SaslErrorCode.IncompleteChallenge is thrown. Without the salt the client cannot compute the salted password (Hi function) and cannot proceed.

Solutions

  1. Log the raw server-first message and confirm which SCRAM attributes it contains
  2. Check the server's authentication database — the user's stored SCRAM salt may be missing/corrupt; re-provision the user
  3. Use a different mechanism (PLAIN over TLS) if the server's SCRAM implementation is incomplete
  4. Catch SaslException with ErrorCode == SaslErrorCode.IncompleteChallenge and fall back to another mechanism

Example fix

// before
client.Authenticate (new SaslMechanismScramSha1 ("user", "pass")); // server omits s=
// after
try {
    client.Authenticate (new SaslMechanismScramSha1 ("user", "pass"));
} catch (SaslException ex) when (ex.ErrorCode == SaslErrorCode.IncompleteChallenge) {
    client.Authenticate (new SaslMechanismPlain ("user", "pass"));
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Cannot inspect the server-first message pre-auth; guard at the mechanism level:
if (!client.AuthenticationMechanisms.Any (m => m.StartsWith ("SCRAM-")))
    usePlainOverTls = true;

Try / catch

try {
    client.Authenticate (scramMechanism);
} catch (SaslException ex) when (ex.ErrorCode == SaslErrorCode.IncompleteChallenge) {
    // server-first message missing salt/nonce/iterations; use another mechanism
}

Prevention

When it happens

Trigger: Calling Challenge() on a SCRAM mechanism when the server-first message (e.g. "r=...,i=4096") is missing the s=<base64-salt> attribute.

Common situations: Non-conformant SCRAM server implementations, servers omitting the salt due to a bug or misconfigured user database entry (no stored salt for that user).

Related errors


AI-assisted analysis of jstedfast/MailKit@9d3859a785 (2026-09-15). Data as JSON: /api/errors/419685fb6330d1e9. Report an issue: GitHub.

Appendix: source

Thrown at MailKit/Security/SaslMechanismScramBase.cs:345

						channelBindingKind = ChannelBindingKind.Unknown;
					}
				}

				input = GetChannelBindingInput (channelBindingKind, AuthorizationId);
				response = Encoding.UTF8.GetBytes (input + client);
				state = LoginState.Final;
				break;
			case LoginState.Final:
				if (token == null)
					throw new SaslException (MechanismName, SaslErrorCode.MissingChallenge, "Server response did not contain any authentication data.");

				server = Encoding.UTF8.GetString (token, startIndex, length);
				var tokens = ParseServerChallenge (server);
				string? salt, nonce, iterations;
				int count;

				if (!tokens.TryGetValue ('s', out salt))
					throw new SaslException (MechanismName, SaslErrorCode.IncompleteChallenge, "Challenge did not contain a salt.");

				if (!tokens.TryGetValue ('r', out nonce))
					throw new SaslException (MechanismName, SaslErrorCode.IncompleteChallenge, "Challenge did not contain a nonce.");

				if (!tokens.TryGetValue ('i', out iterations))
					throw new SaslException (MechanismName, SaslErrorCode.IncompleteChallenge, "Challenge did not contain an iteration count.");

				if (!nonce.StartsWith (cnonce!, StringComparison.Ordinal))
					throw new SaslException (MechanismName, SaslErrorCode.InvalidChallenge, "Challenge contained an invalid nonce.");

				if (!int.TryParse (iterations, NumberStyles.None, CultureInfo.InvariantCulture, out count) || count < 1)
					throw new SaslException (MechanismName, SaslErrorCode.InvalidChallenge, "Challenge contained an invalid iteration count.");

				var password = Encoding.UTF8.GetBytes (SaslPrep (Credentials.Password));
				salted = Hi (password, Convert.FromBase64String (salt), count);
				Array.Clear (password, 0, password.Length);

				input = GetChannelBindingInput (channelBindingKind, AuthorizationId);

View on GitHub (pinned to 9d3859a785)