NTLM-only authentication broken in sspi 0.18.8+ when USE_SESSION_KEY is requested without Kerberos
Summary
Starting in sspi 0.18.8 (PR #600), consumers that request ClientRequestFlags::USE_SESSION_KEY without providing a target SPN get a hard error during initialize_security_context. In 0.18.7, this worked fine — NTLM negotiation proceeded without requiring an SPN. This is a breaking change that affects any SMB client using NTLM-only authentication.
Additionally, the NTLM-on-IP-address fallback added in PR #637 (sspi 0.18.9) does not fully work — the Kerberos TGT-REQ token is still generated even after the protocol is downgraded to NTLM, causing STATUS_LOGON_FAILURE on non-domain-joined servers.
Environment
- sspi versions tested: 0.18.7 (works), 0.18.8 (broken), 0.18.9 (broken)
- SMB client: smb-rs v0.11.1, built without
kerberos feature
- Server: Dell PowerScale (Isilon) OneFS — local user authentication, no Active Directory/Kerberos domain
- Auth method: NTLM only (
auth_methods: { ntlm: true, kerberos: false })
- Connection target: IP address (DNS resolved before SMB connect, e.g.,
//10.120.4.12/share)
Reproduction
Minimal setup
Any SMB client using sspi's Negotiate SSP with:
ClientRequestFlags::USE_SESSION_KEY in context requirements
- No
target_name provided (or target_name provided as an IP-based SPN like cifs/10.120.4.12)
- NTLM-only package list (no Kerberos)
- Server is a non-domain-joined NAS (OneFS, Samba, etc.) with local user accounts
Test matrix (verified)
All tests: same smb-rs 0.11.1, same server, same credentials, same dataset (53 files), fresh SMB sessions each time.
| Test |
sspi version |
SPN provided |
Result |
| 1 |
0.18.7 |
No |
✅ 53/53 success |
| 2 |
0.18.8 |
No |
❌ 0/53 — NoCredentials: Service target name (service principal name) is not provided |
| 3 |
0.18.9 |
No |
❌ 0/53 — Same NoCredentials error |
| 4 |
0.18.9 |
Yes (cifs/10.120.4.12) |
❌ 0/53 — Logon Failure (0xc000006d) |
Bug 1: USE_SESSION_KEY requires SPN even for NTLM (0.18.8+)
Location
src/negotiate/client.rs:70-82 (introduced in PR #600):
match negotiate.state {
NegotiateState::Initial => {
let sname = if builder
.context_requirements
.contains(ClientRequestFlags::USE_SESSION_KEY)
{
let (service_name, service_principal_name) =
parse_target_name(builder.target_name.ok_or_else(|| {
Error::new(
ErrorKind::NoCredentials,
"Service target name (service principal name) is not provided",
)
})?)?;
Some([service_name, service_principal_name])
} else {
None
};
Problem
USE_SESSION_KEY unconditionally requires target_name to be Some. This is incorrect for NTLM — NTLM always derives a session key during authentication regardless of whether USE_SESSION_KEY is requested. The flag is only meaningful for Kerberos, where the session key comes from the service ticket.
Previous behavior (0.18.7)
In src/negotiate.rs:600+, the initialize_security_context function did not check for USE_SESSION_KEY at all. It processed target_name only for NTLM downgrade detection (IP address check) and proceeded directly to protocol negotiation.
Impact
Any consumer that:
- Requests
USE_SESSION_KEY (common — smb-rs always does)
- Doesn't provide a
target_name (because Kerberos isn't being used)
- Uses NTLM-only authentication
...gets a hard NoCredentials error on every session setup.
Bug 2: NTLM-on-IP downgrade still generates Kerberos TGT-REQ token (0.18.9)
Location
src/negotiate/client.rs:68-100 and src/negotiate/generators.rs:48-80
Problem
When a consumer provides an IP-based SPN (e.g., cifs/10.120.4.12), the flow is:
check_target_name_for_ntlm_downgrade (called before NegotiateState::Initial) detects IP → sets negotiate.protocol = NegotiatedProtocol::Ntlm(...) ✅
NegotiateState::Initial → USE_SESSION_KEY check → parses SPN into sname = Some(["cifs", "10.120.4.12"])
generate_mech_type_list(matches!(protocol, Kerberos(_)), ntlm) → correctly uses NTLM mech types (because protocol was downgraded) ✅
generate_neg_token_init(sname, mech_types) → generates a Kerberos TGT-REQ token because sname is Some ❌
In generators.rs:52-72, when sname is Some, a full Kerberos TgtReq message is constructed:
let krb5_neg_token_init = ApplicationTag0(KrbMessage {
krb5_oid: ObjectIdentifierAsn1::from(oids::krb5_user_to_user()),
krb5_token_id: TGT_REQ_TOKEN_ID,
krb_msg: TgtReq {
server_name: PrincipalName { name_string: sname },
},
});
This Kerberos token is embedded in the SPNego NegTokenInit.mech_token field. The server receives an SPNego init that says "I support NTLM" in mech_types but also contains a Kerberos TGT-REQ in mech_token. Non-domain-joined servers (OneFS, Samba with local users) attempt to process the Kerberos token, fail, and return STATUS_LOGON_FAILURE (0xc000006d).
Expected behavior
When the protocol has been downgraded to NTLM (by the IP address check or any other reason), sname should be set to None so that generate_neg_token_init produces a clean NTLM-only SPNego init without a Kerberos mech_token.
Suggested fix
In src/negotiate/client.rs, after the USE_SESSION_KEY → sname extraction, check if the protocol is NTLM and clear the sname:
let sname = if builder
.context_requirements
.contains(ClientRequestFlags::USE_SESSION_KEY)
{
// ... existing SPN parsing ...
Some([service_name, service_principal_name])
} else {
None
};
// Don't include Kerberos TGT-REQ when protocol has been downgraded to NTLM
let sname = if matches!(&negotiate.protocol, NegotiatedProtocol::Ntlm(_)) {
None
} else {
sname
};
Or alternatively, don't require target_name when the negotiated protocol is NTLM — since NTLM doesn't use the SPN for session key derivation, USE_SESSION_KEY should not gate on target_name for NTLM.
Workaround
Pin sspi = "=0.18.7" in Cargo.toml. This is the last version where NTLM-only authentication works without providing an SPN.
Related
NTLM-only authentication broken in sspi 0.18.8+ when
USE_SESSION_KEYis requested without KerberosSummary
Starting in sspi 0.18.8 (PR #600), consumers that request
ClientRequestFlags::USE_SESSION_KEYwithout providing a target SPN get a hard error duringinitialize_security_context. In 0.18.7, this worked fine — NTLM negotiation proceeded without requiring an SPN. This is a breaking change that affects any SMB client using NTLM-only authentication.Additionally, the NTLM-on-IP-address fallback added in PR #637 (sspi 0.18.9) does not fully work — the Kerberos TGT-REQ token is still generated even after the protocol is downgraded to NTLM, causing
STATUS_LOGON_FAILUREon non-domain-joined servers.Environment
kerberosfeatureauth_methods: { ntlm: true, kerberos: false })//10.120.4.12/share)Reproduction
Minimal setup
Any SMB client using sspi's
NegotiateSSP with:ClientRequestFlags::USE_SESSION_KEYin context requirementstarget_nameprovided (ortarget_nameprovided as an IP-based SPN likecifs/10.120.4.12)Test matrix (verified)
All tests: same smb-rs 0.11.1, same server, same credentials, same dataset (53 files), fresh SMB sessions each time.
NoCredentials: Service target name (service principal name) is not providedNoCredentialserrorcifs/10.120.4.12)Logon Failure (0xc000006d)Bug 1:
USE_SESSION_KEYrequires SPN even for NTLM (0.18.8+)Location
src/negotiate/client.rs:70-82(introduced in PR #600):Problem
USE_SESSION_KEYunconditionally requirestarget_nameto beSome. This is incorrect for NTLM — NTLM always derives a session key during authentication regardless of whetherUSE_SESSION_KEYis requested. The flag is only meaningful for Kerberos, where the session key comes from the service ticket.Previous behavior (0.18.7)
In
src/negotiate.rs:600+, theinitialize_security_contextfunction did not check forUSE_SESSION_KEYat all. It processedtarget_nameonly for NTLM downgrade detection (IP address check) and proceeded directly to protocol negotiation.Impact
Any consumer that:
USE_SESSION_KEY(common — smb-rs always does)target_name(because Kerberos isn't being used)...gets a hard
NoCredentialserror on every session setup.Bug 2: NTLM-on-IP downgrade still generates Kerberos TGT-REQ token (0.18.9)
Location
src/negotiate/client.rs:68-100andsrc/negotiate/generators.rs:48-80Problem
When a consumer provides an IP-based SPN (e.g.,
cifs/10.120.4.12), the flow is:check_target_name_for_ntlm_downgrade(called beforeNegotiateState::Initial) detects IP → setsnegotiate.protocol = NegotiatedProtocol::Ntlm(...)✅NegotiateState::Initial→USE_SESSION_KEYcheck → parses SPN intosname = Some(["cifs", "10.120.4.12"])generate_mech_type_list(matches!(protocol, Kerberos(_)), ntlm)→ correctly uses NTLM mech types (because protocol was downgraded) ✅generate_neg_token_init(sname, mech_types)→ generates a Kerberos TGT-REQ token becausesnameisSome❌In
generators.rs:52-72, whensnameisSome, a full KerberosTgtReqmessage is constructed:This Kerberos token is embedded in the SPNego
NegTokenInit.mech_tokenfield. The server receives an SPNego init that says "I support NTLM" inmech_typesbut also contains a Kerberos TGT-REQ inmech_token. Non-domain-joined servers (OneFS, Samba with local users) attempt to process the Kerberos token, fail, and returnSTATUS_LOGON_FAILURE (0xc000006d).Expected behavior
When the protocol has been downgraded to NTLM (by the IP address check or any other reason),
snameshould be set toNoneso thatgenerate_neg_token_initproduces a clean NTLM-only SPNego init without a Kerberos mech_token.Suggested fix
In
src/negotiate/client.rs, after theUSE_SESSION_KEY→snameextraction, check if the protocol is NTLM and clear the sname:Or alternatively, don't require
target_namewhen the negotiated protocol is NTLM — since NTLM doesn't use the SPN for session key derivation,USE_SESSION_KEYshould not gate on target_name for NTLM.Workaround
Pin
sspi = "=0.18.7"inCargo.toml. This is the last version where NTLM-only authentication works without providing an SPN.Related
USE_SESSION_KEY→ SPN requirement (breaking change)USE_SESSION_KEY, only provides SPN withkerberosfeature)