//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: Services/CredentialsManager/Ntlm.sg // // Note: Contains the code that allows the Credentials Manager to use NTLM. // The NTLM algorithms are not implemented in this file; they're in // the Libraries/Ntlm. // using System; using System.Collections; using System.Diagnostics; using System.Threading; using Microsoft.Contracts; using Microsoft.Singularity; using Microsoft.Singularity.Channels; using Microsoft.Singularity.Directory; using Microsoft.Singularity.Io; using Microsoft.SingSharp; using NtlmSupplicant = System.Security.Protocols.Ntlm.NtlmSupplicant; using Ex = Microsoft.Singularity.Security; namespace Microsoft.Singularity.Security.CredentialsManager { class NtlmAuthenticationProtocol : AuthenticationProtocol { public override bool CreateSupplicant([Claims]ServiceContract.Exp! exp, Credentials! credentials, out CredError error) { // The only kind of evidence that NTLM supports is PasswordEvidence. PasswordEvidence pwevidence = credentials.Evidence as PasswordEvidence; if (pwevidence == null) { delete exp; error = CredError.EvidenceTypeNotSupported; return false; } // Parse the username and domain name from the credentials name. string! credentialsName = credentials.Id.CredentialsName; string! username; string! domain; int separator = credentialsName.IndexOf('\\'); if (separator != -1) { // Is it "domain\user"? username = credentialsName.Substring(separator + 1); domain = credentialsName.Substring(0, separator); } else { separator = credentialsName.IndexOf('@'); if (separator != -1) { // Is it "user@domain.foo"? username = credentialsName.Substring(0, separator); domain = credentialsName.Substring(separator + 1); } else { // No, and no. Assume "user", and use a domain of ".", which most implementations // interpret as meaning "this machine" or "this domain". username = credentialsName; domain = "."; } } // Which contract does the client want to use? NtlmSupplicantContract.Exp ntlm_exp = exp as NtlmSupplicantContract.Exp; GssSupplicantContract.Exp gss_exp; if ((ntlm_exp = exp as NtlmSupplicantContract.Exp) != null) { NtlmLegacySupplicantClient.StartServiceThread(ntlm_exp, pwevidence); error = CredError.NoError; return true; } else if ((gss_exp = exp as GssSupplicantContract.Exp) != null) { NtlmGssSupplicantClient.StartServiceThread(gss_exp, username, domain, pwevidence); error = CredError.NoError; return true; } else { delete exp; error = CredError.ContractNotSupported; return false; } } } /// // // This class provides raw access to the NTLM supplicant, using NtlmSupplicantContract. // This is for clients that deal directly with the username, domain name, the NTLM // challenge, and response, all as discrete fields. // // class NtlmLegacySupplicantClient { public static void StartServiceThread([Claims]NtlmSupplicantContract.Exp:Start! exp, PasswordEvidence! evidence) { NtlmLegacySupplicantClient client = new NtlmLegacySupplicantClient(exp, evidence); ThreadStart start = new ThreadStart(client.ThreadServiceRoutine); Thread thread = new Thread(start); thread.Start(); } private NtlmLegacySupplicantClient([Claims]NtlmSupplicantContract.Exp:Start! exp, PasswordEvidence! evidence) { _exp = new TRef(exp); _evidence = evidence; } TRef! _exp; readonly PasswordEvidence! _evidence; void ThreadServiceRoutine() { NtlmSupplicantContract.Exp! exp = _exp.Acquire(); // DebugLine("Service thread started."); try { exp.SendSuccess(); for (;;) { switch receive { case exp.ChannelClosed(): // DebugLine("Channel closed."); return; case exp.GetResponse(exchallenge, type): byte[]! challenge = Bitter.ToByteArray(exchallenge); delete exchallenge; byte[] response; switch (type) { case NtlmResponseType.LanMan: // DebugLine("Computing LanMan response"); response = NtlmSupplicant.ComputeLmResponse(challenge, _evidence.Password); break; case NtlmResponseType.WindowsNt: // DebugLine("Computing NT response"); response = NtlmSupplicant.ComputeNtResponse(challenge, _evidence.Password); break; default: DebugLine("Invalid arguments -- response type not supported"); response = null; break; } if (response != null) { byte[]! in ExHeap exresponse = Bitter.FromByteArray(response); exp.SendResponse(exresponse); } else { exp.SendRequestFailed(CredError.InvalidArguments); } break; } } } catch (Exception ex) { DebugLine("EXCEPTION in NtlmLegacySupplicantClient service thread!"); Util.DumpException(ex); } finally { delete exp; } } void DebugLine(string! line) { DebugStub.WriteLine("CREDMGR[NtlmLegacy]: " + line); } void DebugLine(string! format, params object[]! args) { DebugLine(String.Format(format, args)); } } /// // // This class provides access to the NTLM supplicant using GSS. GSS is a token-exchange model. // This implementation uses standard NTLMSSP tokens, and is compatible with the Windows NTLM SSP // implementation. // // class NtlmGssSupplicantClient { public static void StartServiceThread( [Claims]GssSupplicantContract.Exp:Start! exp, string! username, string! domain, PasswordEvidence! evidence) { NtlmGssSupplicantClient client = new NtlmGssSupplicantClient(exp, username, domain, evidence); ThreadStart start = new ThreadStart(client.ThreadServiceRoutine); Thread thread = new Thread(start); thread.Start(); } private NtlmGssSupplicantClient( [Claims]GssSupplicantContract.Exp:Start! exp, string! username, string! domain, PasswordEvidence! evidence) { _exp = new TRef(exp); _evidence = evidence; _workstation = "SINGULARITY"; _username = username; _domain = domain; } TRef! _exp; readonly PasswordEvidence! _evidence; readonly string! _username; readonly string! _domain; readonly string! _workstation; void ThreadServiceRoutine() { GssSupplicantContract.Exp! exp = _exp.Acquire(); // DebugLine("Service thread started."); try { // In NTLM/GSS, the supplicant sends the first message (Negotiate). // This message contains the domain name and hostname of the local computer, // which Singularity doesn't really support right now. So we'll use fixed values. byte[]! negotiate_token; try { negotiate_token = NtlmSupplicant.GetNegotiate( 0, "WORKGROUP", _workstation); } catch (Exception ex) { DebugLine("The NTLM supplicant threw an exception while trying to build the Negotiate token."); Util.DumpException(ex); return; } // DebugLine("Sending Negotiate token"); exp.SendFirstToken(Bitter.FromByteArray(negotiate_token)); // The supplicant channel is now in the WaitingForToken state. switch receive { case exp.ChannelClosed(): // DebugLine("Channel closed."); return; case exp.AcceptToken(byte[]! in ExHeap exchallenge_token): byte[]! challenge_token = Bitter.ToByteArray(exchallenge_token); delete exchallenge_token; // DebugLine("Received Challenge token."); byte[]! response_token; try { response_token = NtlmSupplicant.GetResponse( challenge_token, _domain, _username, _workstation, _evidence.Password); } catch (Exception ex) { DebugLine("The NTLM supplicant threw an exception while processing the Challenge token."); Util.DumpException(ex); exp.SendAuthenticationFailed(GssErrorCode.InternalError); goto failed_state; } // DebugLine("Sending ContinueNeeded, with the Response token."); byte[]! in ExHeap ex_response_token = Bitter.FromByteArray(response_token); exp.SendCompleteWithToken(ex_response_token); break; } // // At this point, we are in the "succeeded" state. // Which actually resembles the "failed" state, but this state will probably // have some real messages later. // // DebugLine("Authentication is complete, at least from our point of view."); for (;;) { switch receive { case exp.ChannelClosed(): return; } } failed_state: for (;;) { switch receive { case exp.ChannelClosed(): return; } } } catch (Exception ex) { DebugLine("EXCEPTION in NtlmSupplicant service thread!"); Util.DumpException(ex); } finally { delete exp; } } void DebugLine(string! line) { DebugStub.WriteLine("CREDMGR[NtlmGss]: " + line); } void DebugLine(string! format, params object[]! args) { DebugLine(String.Format(format, args)); } } }