diff --git a/src/credssp/mod.rs b/src/credssp/mod.rs index 4e8ffe9b..f7acaffc 100644 --- a/src/credssp/mod.rs +++ b/src/credssp/mod.rs @@ -166,6 +166,7 @@ impl EarlyUserAuthResult { enum CredSspState { NegoToken, AuthInfo, + PubKeyInfo, Final, } @@ -412,7 +413,7 @@ impl CredSspClient { Ok(ClientState::FinalMessage(ts_request)) } - CredSspState::Final => Err(Error::new( + CredSspState::Final | CredSspState::PubKeyInfo => Err(Error::new( ErrorKind::OutOfSequence, "CredSSP client's 'process' method must not be fired after the 'Finished' state", )), @@ -486,6 +487,31 @@ impl + Send> CredSspServe }) } + fn exchange_pub_key_auth( + &mut self, + pub_key_auth: &[u8], + client_nonce: &Option<[u8; NONCE_SIZE]>, + ) -> crate::Result> { + let peer_version = self + .context + .as_ref() + .unwrap() + .peer_version + .expect("an decrypt public key server function cannot be fired without any incoming TSRequest"); + + let context = self.context.as_mut().unwrap(); + + context.decrypt_public_key( + &self.public_key, + pub_key_auth, + EndpointType::Server, + client_nonce, + peer_version, + )?; + + context.encrypt_public_key(&self.public_key, EndpointType::Server, client_nonce, peer_version) + } + #[instrument(fields(state = ?self.state), skip_all)] pub fn process( &mut self, @@ -628,42 +654,22 @@ impl + Send> CredSspServe self.context.as_mut().unwrap().sspi_context.complete_auth_token(&mut []), ts_request ); - ts_request.nego_tokens = None; - let pub_key_auth = try_cred_ssp_server!( - ts_request.pub_key_auth.take().ok_or_else(|| { - Error::new( - ErrorKind::InvalidToken, - String::from("CredSSP server expected an encrypted public key"), - ) - }), - ts_request - ); - let peer_version = self.context.as_ref().unwrap().peer_version.expect( - "an decrypt public key server function cannot be fired without any incoming TSRequest", - ); - try_cred_ssp_server!( - self.context.as_mut().unwrap().decrypt_public_key( - self.public_key.as_ref(), - pub_key_auth.as_ref(), - EndpointType::Server, - &ts_request.client_nonce, - peer_version, - ), - ts_request - ); - let pub_key_auth = try_cred_ssp_server!( - self.context.as_mut().unwrap().encrypt_public_key( - self.public_key.as_ref(), - EndpointType::Server, - &ts_request.client_nonce, - peer_version, - ), - ts_request - ); - ts_request.pub_key_auth = Some(pub_key_auth); + if let Some(pub_key_auth) = ts_request.pub_key_auth.as_ref() { + ts_request.nego_tokens = None; + + let pub_key_auth = try_cred_ssp_server!( + self.exchange_pub_key_auth(pub_key_auth, &ts_request.client_nonce), + ts_request + ); + ts_request.pub_key_auth = Some(pub_key_auth); + + self.state = CredSspState::AuthInfo; + } else { + ts_request.nego_tokens = Some(output_token.remove(0).buffer); - self.state = CredSspState::AuthInfo; + self.state = CredSspState::PubKeyInfo; + } } result => { try_cred_ssp_server!( @@ -675,10 +681,34 @@ impl + Send> CredSspServe ) } }; + self.credentials_handle = credentials_handle; Ok(ServerState::ReplyNeeded(ts_request)) } + CredSspState::PubKeyInfo => { + ts_request.nego_tokens = None; + + let pub_key_auth = try_cred_ssp_server!( + ts_request.pub_key_auth.take().ok_or_else(|| { + Error::new( + ErrorKind::InvalidToken, + String::from("CredSSP server expected an encrypted public key"), + ) + }), + ts_request + ); + + let pub_key_auth = try_cred_ssp_server!( + self.exchange_pub_key_auth(&pub_key_auth, &ts_request.client_nonce), + ts_request + ); + ts_request.pub_key_auth = Some(pub_key_auth); + + self.state = CredSspState::AuthInfo; + + Ok(ServerState::ReplyNeeded(ts_request)) + } CredSspState::Final => Err(ServerError { ts_request: Some(Box::new(ts_request)), error: Error::new( diff --git a/tests/sspi/client_server/credssp.rs b/tests/sspi/client_server/credssp.rs index 23dc8db6..e7d46679 100644 --- a/tests/sspi/client_server/credssp.rs +++ b/tests/sspi/client_server/credssp.rs @@ -178,3 +178,44 @@ fn credssp_kerberos() { run_credssp(&mut client, &mut server, &identity_1, &mut network_client); } + +#[test] +fn credssp_negotiate_ntlm() { + let auth_identity = AuthIdentity { + username: Username::parse("test_user").unwrap(), + password: Secret::from("test_password".to_owned()), + }; + let credentials = Credentials::AuthIdentity(auth_identity.clone()); + + let mut client = CredSspClient::new( + PUBLIC_KEY.to_vec(), + credentials.clone(), + CredSspMode::WithCredentials, + ClientMode::Negotiate(NegotiateConfig::new( + Box::new(NtlmConfig { + client_computer_name: Some("DESKTOP-3D83IAN.example.com".to_owned()), + }), + Some("ntlm,!kerberos,!pku2u".to_owned()), + "DESKTOP-3D83IAN.example.com".to_owned(), + )), + TARGET_NAME.to_owned(), + ) + .unwrap(); + + let mut server = CredSspServer::new( + PUBLIC_KEY.to_vec(), + CredentialsProxyImpl::new(&auth_identity), + ServerMode::Negotiate(NegotiateConfig::new( + Box::new(NtlmConfig { + client_computer_name: Some("DESKTOP-3D83IAN.example.com".to_owned()), + }), + Some("ntlm,!kerberos,!pku2u".to_owned()), + "SERVER.example.com".to_owned(), + )), + ) + .unwrap(); + + let mut network_client = NetworkClientMock { kdc: KdcMock::empty() }; + + run_credssp(&mut client, &mut server, &auth_identity, &mut network_client); +}