/// <summary> /// accept the authenticate packet, if failed, throw exception. /// </summary> /// <param name="authenticatePacket">authenciate packet</param> /// <exception cref="InvalidOperationException">INVALID message error</exception> /// <exception cref="InvalidOperationException">mic of authenticate packet is invalid</exception> private void AcceptAuthenticatePacket(NlmpAuthenticatePacket authenticatePacket) { // save the authenticate to valid the mic. this.authenticate = authenticatePacket; // retrieve the client authenticate information ClientAuthenticateInfomation authenticateInformation = RetrieveClientAuthenticateInformation(authenticatePacket); // update the version of context this.version = authenticateInformation.Version; if (authenticateInformation.ClientTime != 0) { this.systemTime = authenticateInformation.ClientTime; } byte[] exportedSessionKey = null; if (this.nlmpServer.Context.ClientCredential == null) { if (verifyAuthenticatePacketInDc == null) { throw new InvalidOperationException( "In domain environment, the delegate method " + "VerifyAuthenticatePacketInDcMethod should not be null"); } // in domain environment, the authentication is hosted in Active Directory, // the response pair MUST be sent to DC to verify [MS-APDS] section 3.1.5 // DC will return the authentication result and the session key DcValidationInfo dcValidationInfo = verifyAuthenticatePacketInDc( authenticatePacket, this.challenge.Payload.ServerChallenge); if (dcValidationInfo.status != NtStatus.STATUS_SUCCESS) { throw new InvalidOperationException( string.Format( "verify authentication packet in DC failed, NtStatus value ={0}", dcValidationInfo.status.ToString())); } exportedSessionKey = dcValidationInfo.sessionKey; } else { // in workgroup environment. the authentication is hosted locally. authenticateInformation = VerifyAuthenticatePacketLocally( authenticatePacket, authenticateInformation, out exportedSessionKey); } // save keys this.nlmpServer.Context.ExportedSessionKey = exportedSessionKey; // initialize keys and handles InitializeKeys(exportedSessionKey); }
/// <summary> /// get the security token,using the token from server /// </summary> /// <param name="serverToken">the token from server challenge</param> /// <returns>the security token</returns> private byte[] GetSecurityToken( byte[] serverToken ) { if (this.client == null) { throw new InvalidOperationException("The client is null! You must initialize this field first!"); } // the challenge packet from server NlmpChallengePacket challenge = new NlmpChallengePacket(serverToken); NegotiateTypes flags = InitializeNegotiateFlags(challenge.Payload.NegotiateFlags); // the target info ICollection <AV_PAIR> targetInfo = InitializeTargetInfo(challenge.Payload.TargetInfo); // responseKeyLM byte[] responseKeyLM; // lmChallengeResponse byte[] lmChallengeResponse; // ntChallengeResponse byte[] ntChallengeResponse; // initialize the challenge response InitializeChallengeResponse( flags, challenge, targetInfo, out responseKeyLM, out lmChallengeResponse, out ntChallengeResponse); // encryptedRandomSessionKey byte[] encryptedRandomSessionKey = null; // exportedSessionKey byte[] exportedSessionKey = null; // initialize keys InitializeKeys( flags, challenge, responseKeyLM, lmChallengeResponse, out encryptedRandomSessionKey, out exportedSessionKey); // save the exported sessionkey this.client.Context.ExportedSessionKey = exportedSessionKey; // create challenge packet NlmpAuthenticatePacket packet = this.client.CreateAuthenticatePacket( flags, NlmpUtility.GetVersion(), lmChallengeResponse, ntChallengeResponse, this.currentActiveCredential.DomainName, this.currentActiveCredential.AccountName, Environment.MachineName, encryptedRandomSessionKey); // initialize the mic of challenge packet InitializeChallengeMIC(exportedSessionKey, targetInfo, packet, challenge); return(packet.ToBytes()); }
/// <summary> /// valid the authenticate message /// </summary> /// <param name="authenticate"> /// the authenticate packet of client, contains the challenge response calculated by client /// </param> /// <param name="expectedNtChallengeResponse">the nt challenge response calculated by server</param> /// <param name="expectedLmChallengeResponse">the lm challenge response calculated by server</param> private static void ValidAuthenticateMessage( NlmpAuthenticatePacket authenticate, byte[] expectedNtChallengeResponse, byte[] expectedLmChallengeResponse) { if (authenticate.Payload.NtChallengeResponseFields.Len == 0 || !ArrayUtility.CompareArrays <byte>(expectedNtChallengeResponse, authenticate.Payload.NtChallengeResponse)) { if (authenticate.Payload.LmChallengeResponseFields.Len == 0 || !ArrayUtility.CompareArrays <byte>(expectedLmChallengeResponse, authenticate.Payload.LmChallengeResponse)) { throw new InvalidOperationException("INVALID message error"); } } }
/// <summary> /// verify the implicit ntlm session security token. /// this api is invoked by protocol need the implicit Ntlm authenticate, such as CifsSdk and the SmbSdk with /// none extended session security. /// </summary> /// <param name="accountName">the user name which determined by the security context of the user initiating the /// connection to share</param> /// <param name="domainName">the Primary Domain of the user</param> /// <param name="serverTime">the time of the server</param> /// <param name="caseInsensitivePassword">the NtChallengeResponse</param> /// <param name="caseSensitivePassword">the LmChallengeResponse</param> /// <returns>success or fail</returns> public bool VerifySecurityToken( //string workstationName, string accountName, string domainName, ulong serverTime, byte[] caseInsensitivePassword, byte[] caseSensitivePassword) { if (string.IsNullOrEmpty(accountName)) { return(false); } this.systemTime = serverTime; NlmpAuthenticatePacket authenticatePacket = new NlmpAuthenticatePacket(); authenticatePacket.SetNtChallengeResponse(caseSensitivePassword); authenticatePacket.SetLmChallengeResponse(caseInsensitivePassword); authenticatePacket.SetDomainName(domainName); authenticatePacket.SetUserName(accountName); authenticatePacket.SetWorkstation(string.Empty); authenticatePacket.SetEncryptedRandomSessionKey(new byte[0]); try { this.AcceptAuthenticatePacket(authenticatePacket); } catch (InvalidOperationException) { return(false); } catch (ArgumentException) // caseSensitivePassword or caseInsensitivePassword is not long enough { return(false); } return(true); }
/// <summary> /// initialize the mic of challenge packet /// </summary> /// <param name="exportedSessionKey">the exported session key</param> /// <param name="targetInfo">the target info contains av pairs.</param> /// <param name="authenticatePacket">the authenticate packet</param> /// <param name="challengePacket">the challenge packet</param> private void InitializeChallengeMIC( byte[] exportedSessionKey, ICollection <AV_PAIR> targetInfo, NlmpAuthenticatePacket authenticatePacket, NlmpChallengePacket challengePacket ) { if (NlmpUtility.AvPairContains(targetInfo, AV_PAIR_IDs.MsvAvTimestamp)) { // update mic with security algorithm byte[] mic = null; // if connectionless, this.negotiate is null. mic = NlmpUtility.GetMic(exportedSessionKey, this.negotiate, challengePacket, authenticatePacket); // get payload of packet AUTHENTICATE_MESSAGE payload = authenticatePacket.Payload; // update mic to payload payload.MIC = mic; // update the meaningful payload to packet authenticatePacket.Payload = payload; } }
/// <summary> /// copy constructor. /// </summary> /// <param name="stackPacket">the source packet</param> /// <exception cref="ArgumentNullException">the stackPacket.payload.MIC must not be null</exception> /// <exception cref="ArgumentException">the stackPacket.payload.MIC.Length is wrong</exception> public NlmpAuthenticatePacket( NlmpAuthenticatePacket stackPacket ) : base(stackPacket) { this.payload = stackPacket.payload; if (stackPacket.payload.MIC == null) { throw new ArgumentNullException("stackPacket", "the stackPacket.payload.MIC must not be null"); } if (stackPacket.payload.MIC.Length != NlmpUtility.AUTHENTICATE_MESSAGE_MIC_CONST_SIZE) { throw new ArgumentException( string.Format("the stackPacket.payload.MIC.Length must be {0}, actually {1}", NlmpUtility.AUTHENTICATE_MESSAGE_MIC_CONST_SIZE, stackPacket.payload.MIC.Length), "stackPacket"); } this.payload.MIC = new byte[stackPacket.payload.MIC.Length]; Array.Copy(stackPacket.payload.MIC, this.payload.MIC, stackPacket.payload.MIC.Length); if (stackPacket.payload.DomainName != null) { this.payload.DomainName = new byte[stackPacket.payload.DomainName.Length]; Array.Copy( stackPacket.payload.DomainName, this.payload.DomainName, stackPacket.payload.DomainName.Length); } if (stackPacket.payload.UserName != null) { this.payload.UserName = new byte[stackPacket.payload.UserName.Length]; Array.Copy( stackPacket.payload.UserName, this.payload.UserName, stackPacket.payload.UserName.Length); } if (stackPacket.payload.Workstation != null) { this.payload.Workstation = new byte[stackPacket.payload.Workstation.Length]; Array.Copy( stackPacket.payload.Workstation, this.payload.Workstation, stackPacket.payload.Workstation.Length); } if (stackPacket.payload.LmChallengeResponse != null) { this.payload.LmChallengeResponse = new byte[stackPacket.payload.LmChallengeResponse.Length]; Array.Copy( stackPacket.payload.LmChallengeResponse, this.payload.LmChallengeResponse, stackPacket.payload.LmChallengeResponse.Length); } if (stackPacket.payload.NtChallengeResponse != null) { this.payload.NtChallengeResponse = new byte[stackPacket.payload.NtChallengeResponse.Length]; Array.Copy( stackPacket.payload.NtChallengeResponse, this.payload.NtChallengeResponse, stackPacket.payload.NtChallengeResponse.Length); } if (stackPacket.payload.EncryptedRandomSessionKey != null) { this.payload.EncryptedRandomSessionKey = new byte[stackPacket.payload.EncryptedRandomSessionKey.Length]; Array.Copy( stackPacket.payload.EncryptedRandomSessionKey, this.payload.EncryptedRandomSessionKey, stackPacket.payload.EncryptedRandomSessionKey.Length); } }
/// <summary> /// this function create an authenticate packet. /// after client received the challenge packet from server, client create an authenticate packet to server. /// the authenticate packet contains the authentication information of user that is generated by the credential /// of user. /// this function does not set the mic field of packet. it must be set manually if need. /// </summary> /// <param name="negotiateFlags">this flags indicates the capabilities that client supports.</param> /// <param name="version"> /// This structure is used for debugging purposes only. In normal (non-debugging) protocol messages, it is /// ignored and does not affect the NTLM message processing. /// </param> /// <param name="lmChallengeResponse"> /// An LM_RESPONSE or LMv2_RESPONSE structure that contains the computed LM response to the challenge. If /// NTLM v2 authentication is configured, LmChallengeResponse MUST be an LMv2_RESPONSE structure. Otherwise, /// it MUST be an LM_RESPONSE structure. /// </param> /// <param name="ntChallengeResponse"> /// An NTLM_RESPONSE or NTLMv2_RESPONSE structure that contains the computed NT response to the challenge. /// If NTLM v2 authentication is configured, NtChallengeResponse MUST be an NTLMv2_RESPONSE. Otherwise, it /// MUST be an NTLM_RESPONSE structure. /// </param> /// <param name="domainName"> /// The domain or computer name hosting the user account. DomainName MUST be encoded in the negotiated /// character set. This param can not be null. /// </param> /// <param name="userName"> /// The name of the user to be authenticated. UserName MUST be encoded in the negotiated character set. This /// param can not be null. /// </param> /// <param name="workstation"> /// The name of the computer to which the user is logged on. Workstation MUST be encoded in the negotiated /// character set. This param can not be null. /// </param> /// <param name="encryptedRandomSessionKey"> /// The client's encrypted random session key. This param can be null. /// </param> /// <returns>the authenticate packet</returns> /// <exception cref="System.ArgumentNullException"> /// when LM is using, the ntChallengeResponse should be null! /// </exception> public NlmpAuthenticatePacket CreateAuthenticatePacket( NegotiateTypes negotiateFlags, VERSION version, byte[] lmChallengeResponse, byte[] ntChallengeResponse, string domainName, string userName, string workstation, byte[] encryptedRandomSessionKey ) { #region Parameter check if (domainName == null) { throw new ArgumentNullException("domainName"); } if (userName == null) { throw new ArgumentNullException("userName"); } if (workstation == null) { throw new ArgumentNullException("workstation"); } if (NlmpUtility.IsLm(negotiateFlags) && ntChallengeResponse != null) { throw new ArgumentException( "when LM is using, the ntChallengeResponse should be null!", "ntChallengeResponse"); } #endregion NlmpAuthenticatePacket packet = new NlmpAuthenticatePacket(); // update flags packet.SetNegotiateFlags(negotiateFlags); // if version required, update version if (NlmpUtility.IsVersionRequired(negotiateFlags)) { packet.SetVersion(version); } // update domain name packet.SetDomainName(domainName); // update user name packet.SetUserName(userName); // if version required, update workstation if (NlmpUtility.IsVersionRequired(negotiateFlags)) { packet.SetWorkstation(workstation); } // update lmChallengeResponse packet.SetLmChallengeResponse(lmChallengeResponse); // update ntChallengeResponse packet.SetNtChallengeResponse(ntChallengeResponse); // update encryptedRandomSessionKey packet.SetEncryptedRandomSessionKey(encryptedRandomSessionKey); return packet; }
/// <summary> /// retrieve the domain name from client. client encode the domain name in the authenticate packet. /// </summary> /// <param name="authenticatePacket">the authenticate packet contains the domain name</param> /// <returns>the authentication information of client</returns> private ClientAuthenticateInfomation RetrieveClientAuthenticateInformation( NlmpAuthenticatePacket authenticatePacket) { ClientAuthenticateInfomation authenticateInformation = new ClientAuthenticateInfomation(); // retrieve the version of client if (authenticatePacket.Payload.NtChallengeResponseFields.Len == NTLM_V1_NT_CHALLENGE_RESPONSE_LENGTH) { authenticateInformation.Version = NlmpVersion.v1; } else { authenticateInformation.Version = NlmpVersion.v2; } // retrieve the client challenge if (authenticateInformation.Version == NlmpVersion.v1) { authenticateInformation.ClientChallenge = BitConverter.ToUInt64(ArrayUtility.SubArray <byte>( authenticatePacket.Payload.LmChallengeResponse, 0, TIME_CLIENT_CHALLENGE_LENGTH), 0); } else { authenticateInformation.ClientChallenge = BitConverter.ToUInt64( ArrayUtility.SubArray <byte>(authenticatePacket.Payload.NtChallengeResponse, NTLM_V2_CLIENT_CHALLENGE_OFFSET_IN_NT_CHALLENGE_RESPONSE, TIME_CLIENT_CHALLENGE_LENGTH), 0); } // retrieve the domain name of client if (NlmpUtility.IsUnicode(authenticatePacket.Payload.NegotiateFlags)) { authenticateInformation.DomainName = Encoding.Unicode.GetString(authenticatePacket.Payload.DomainName); } else { authenticateInformation.DomainName = Encoding.ASCII.GetString(authenticatePacket.Payload.DomainName); } // retrieve the user name of client if (NlmpUtility.IsUnicode(authenticatePacket.Payload.NegotiateFlags)) { authenticateInformation.UserName = Encoding.Unicode.GetString(authenticatePacket.Payload.UserName); } else { authenticateInformation.UserName = Encoding.ASCII.GetString(authenticatePacket.Payload.UserName); } // retrieve the server name of client if (authenticateInformation.Version == NlmpVersion.v2) { authenticateInformation.ServerName = ArrayUtility.SubArray <byte>(authenticatePacket.Payload.NtChallengeResponse, NTLM_V2_SERVER_NAME_OFFSET_IN_NT_CHALLENGE_RESPONSE, authenticatePacket.Payload.NtChallengeResponseFields.Len - NTLM_V2_SERVER_NAME_OFFSET_IN_NT_CHALLENGE_RESPONSE - NTLM_V2_SERVER_NAME_RESERVED_LENGTH_IN_NT_CHALLENGE_RESPONSE); } // retrieve the time of client ICollection <AV_PAIR> targetInfo = NlmpUtility.BytesGetAvPairCollection(this.challenge.Payload.TargetInfo); // retrieve the time authenticateInformation.ClientTime = NlmpUtility.GetTime(targetInfo); // if server did not response the timestamp, use the client time stamp if (!NlmpUtility.AvPairContains(targetInfo, AV_PAIR_IDs.MsvAvTimestamp) && authenticateInformation.Version == NlmpVersion.v2) { authenticateInformation.ClientTime = BitConverter.ToUInt64( ArrayUtility.SubArray <byte>(authenticatePacket.Payload.NtChallengeResponse, NTLM_V2_TIME_STAMP_OFFSET_IN_NT_CHALLENGE_RESPONSE, TIME_STAMP_LENGTH), 0); } return(authenticateInformation); }
/// <summary> /// verify the implicit ntlm session security token. /// this api is invoked by protocol need the implicit Ntlm authenticate, such as CifsSdk and the SmbSdk with /// none extended session security. /// </summary> /// <param name="accountName">the user name which determined by the security context of the user initiating the /// connection to share</param> /// <param name="domainName">the Primary Domain of the user</param> /// <param name="serverTime">the time of the server</param> /// <param name="caseInsensitivePassword">the NtChallengeResponse</param> /// <param name="caseSensitivePassword">the LmChallengeResponse</param> /// <returns>success or fail</returns> public bool VerifySecurityToken( //string workstationName, string accountName, string domainName, ulong serverTime, byte[] caseInsensitivePassword, byte[] caseSensitivePassword) { if (string.IsNullOrEmpty(accountName)) { return false; } this.systemTime = serverTime; NlmpAuthenticatePacket authenticatePacket = new NlmpAuthenticatePacket(); authenticatePacket.SetNtChallengeResponse(caseSensitivePassword); authenticatePacket.SetLmChallengeResponse(caseInsensitivePassword); authenticatePacket.SetDomainName(domainName); authenticatePacket.SetUserName(accountName); authenticatePacket.SetWorkstation(string.Empty); authenticatePacket.SetEncryptedRandomSessionKey(new byte[0]); try { this.AcceptAuthenticatePacket(authenticatePacket); } catch (InvalidOperationException) { return false; } catch (ArgumentException) // caseSensitivePassword or caseInsensitivePassword is not long enough { return false; } return true; }
/// <summary> /// Verify the authenticate packet locally /// </summary> /// <param name="authenticatePacket">actual authenticate packet</param> /// <param name="authenticateInformation">expected authenticate information</param> /// <param name="exportedSessionKey">exported session key</param> /// <returns></returns> private ClientAuthenticateInfomation VerifyAuthenticatePacketLocally( NlmpAuthenticatePacket authenticatePacket, ClientAuthenticateInfomation authenticateInformation, out byte[] exportedSessionKey) { // valid user name if (authenticateInformation.UserName.ToUpper() != this.nlmpServer.Context.ClientCredential.AccountName.ToUpper()) { throw new InvalidOperationException( "the user name is invalid!" + " the user name retrieved form authenticate packet is not equal to the context."); } // calc the basekeys byte[] responseKeyLm; byte[] expectedNtChallengeResponse; byte[] expectedLmChallengeResponse; byte[] sessionBaseKey; byte[] keyExchangeKey; CalculateBaseKeys( authenticateInformation.ClientChallenge, this.systemTime, authenticateInformation.ServerName, authenticateInformation.DomainName, authenticateInformation.UserName, this.nlmpServer.Context.ClientCredential.Password, out responseKeyLm, out expectedNtChallengeResponse, out expectedLmChallengeResponse, out sessionBaseKey, out keyExchangeKey); // valid message ValidAuthenticateMessage(authenticatePacket, expectedNtChallengeResponse, expectedLmChallengeResponse); // generate keys. if (NlmpUtility.IsKeyExch(this.nlmpServer.Context.NegFlg)) { exportedSessionKey = NlmpUtility.RC4( keyExchangeKey, authenticatePacket.Payload.EncryptedRandomSessionKey); } else { exportedSessionKey = keyExchangeKey; } // validate mic byte[] messageMic = authenticatePacket.Payload.MIC; byte[] zeroMic = new byte[16]; if (messageMic != null && !ArrayUtility.CompareArrays<byte>(messageMic, zeroMic)) { AUTHENTICATE_MESSAGE payload = authenticatePacket.Payload; payload.MIC = zeroMic; authenticatePacket.Payload = payload; byte[] mic = NlmpUtility.GetMic(exportedSessionKey, this.negotiate, this.challenge, this.authenticate); if (!ArrayUtility.CompareArrays<byte>(messageMic, mic)) { throw new InvalidOperationException("mic of authenticate packet is invalid"); } } return authenticateInformation; }
/// <summary> /// Construct Nlmp pass-through network logon information structure /// from client NTLM authenticate response message /// </summary> /// <param name="parameterControl"> /// A set of bit flags that contain information pertaining to the logon validation processing. /// </param> /// <param name="nlmpAuthenticatePacket"> /// nlmp authenticate response packet sent from client machine /// </param> /// <param name="lmChallenge"> /// nlmp challenge sent from server to client /// </param> /// <returns> /// Nlmp pass-through network logon information /// </returns> /// <exception cref="ArgumentNullException"> /// Thrown when nlmpAuthenticatePacket or lmChallenge is null. /// </exception> /// <exception cref="ArgumentException"> /// Thrown when the length of lmChallenge is not equal to 8 bytes /// </exception> public static _NETLOGON_LEVEL CreateNlmpNetworkLogonInfo( NrpcParameterControlFlags parameterControl, NlmpAuthenticatePacket nlmpAuthenticatePacket, byte[] lmChallenge ) { if (nlmpAuthenticatePacket == null) { throw new ArgumentNullException("nlmpAuthenticatePacket"); } if (lmChallenge == null) { throw new ArgumentNullException("lmChallenge"); } // ServerChallenge (8 bytes): A 64-bit value that contains the NTLM challenge. // The challenge is a 64-bit nonce. The processing of the // ServerChallenge is specified in sections 3.1.5 and 3.2.5. if (lmChallenge.Length != NLMP_SERVER_CHALLENGE_LENGTH) { throw new ArgumentException( "the length of lmChallenge should be 8 bytes", "lmChallenge"); } string domainName; string userName; string logonWorkStation; _NETLOGON_LEVEL netLogonLevel = new _NETLOGON_LEVEL(); if (nlmpAuthenticatePacket.Payload.DomainName != null) { domainName = Encoding.Unicode.GetString(nlmpAuthenticatePacket.Payload.DomainName); } else { throw new ArgumentException( "DomainName field should not be null", "nlmpAuthenticatePacket"); } if (nlmpAuthenticatePacket.Payload.UserName != null) { userName = Encoding.Unicode.GetString(nlmpAuthenticatePacket.Payload.UserName); } else { throw new ArgumentException( "UserName field should not be null", "nlmpAuthenticatePacket"); } if (nlmpAuthenticatePacket.Payload.Workstation != null) { logonWorkStation = Encoding.Unicode.GetString(nlmpAuthenticatePacket.Payload.Workstation); } else { throw new ArgumentException( "WorkStation field should not be null", "nlmpAuthenticatePacket"); } //Identity: A NETLOGON_LOGON_IDENTITY_INFO structure, as specified in section MS-NRPC 2.2.1.4.15, //that contains information about the logon identity. _NETLOGON_LOGON_IDENTITY_INFO identityInfo = NrpcUtility.CreateNetlogonIdentityInfo( parameterControl, domainName, userName, logonWorkStation); netLogonLevel.LogonNetwork = new _NETLOGON_NETWORK_INFO[1]; netLogonLevel.LogonNetwork[0].Identity = identityInfo; netLogonLevel.LogonNetwork[0].LmChallenge = new LM_CHALLENGE(); netLogonLevel.LogonNetwork[0].LmChallenge.data = lmChallenge; netLogonLevel.LogonNetwork[0].LmChallengeResponse = NrpcUtility.CreateString(nlmpAuthenticatePacket.Payload.LmChallengeResponse); netLogonLevel.LogonNetwork[0].NtChallengeResponse = NrpcUtility.CreateString(nlmpAuthenticatePacket.Payload.NtChallengeResponse); return netLogonLevel; }
/// <summary> /// retrieve the domain name from client. client encode the domain name in the authenticate packet. /// </summary> /// <param name="authenticatePacket">the authenticate packet contains the domain name</param> /// <returns>the authentication information of client</returns> private ClientAuthenticateInfomation RetrieveClientAuthenticateInformation( NlmpAuthenticatePacket authenticatePacket) { ClientAuthenticateInfomation authenticateInformation = new ClientAuthenticateInfomation(); // retrieve the version of client if (authenticatePacket.Payload.NtChallengeResponseFields.Len == NTLM_V1_NT_CHALLENGE_RESPONSE_LENGTH) { authenticateInformation.Version = NlmpVersion.v1; } else { authenticateInformation.Version = NlmpVersion.v2; } // retrieve the client challenge if (authenticateInformation.Version == NlmpVersion.v1) { authenticateInformation.ClientChallenge = BitConverter.ToUInt64(ArrayUtility.SubArray<byte>( authenticatePacket.Payload.LmChallengeResponse, 0, TIME_CLIENT_CHALLENGE_LENGTH), 0); } else { authenticateInformation.ClientChallenge = BitConverter.ToUInt64( ArrayUtility.SubArray<byte>(authenticatePacket.Payload.NtChallengeResponse, NTLM_V2_CLIENT_CHALLENGE_OFFSET_IN_NT_CHALLENGE_RESPONSE, TIME_CLIENT_CHALLENGE_LENGTH), 0); } // retrieve the domain name of client if (NlmpUtility.IsUnicode(authenticatePacket.Payload.NegotiateFlags)) { authenticateInformation.DomainName = Encoding.Unicode.GetString(authenticatePacket.Payload.DomainName); } else { authenticateInformation.DomainName = Encoding.ASCII.GetString(authenticatePacket.Payload.DomainName); } // retrieve the user name of client if (NlmpUtility.IsUnicode(authenticatePacket.Payload.NegotiateFlags)) { authenticateInformation.UserName = Encoding.Unicode.GetString(authenticatePacket.Payload.UserName); } else { authenticateInformation.UserName = Encoding.ASCII.GetString(authenticatePacket.Payload.UserName); } // retrieve the server name of client if (authenticateInformation.Version == NlmpVersion.v2) { authenticateInformation.ServerName = ArrayUtility.SubArray<byte>(authenticatePacket.Payload.NtChallengeResponse, NTLM_V2_SERVER_NAME_OFFSET_IN_NT_CHALLENGE_RESPONSE, authenticatePacket.Payload.NtChallengeResponseFields.Len - NTLM_V2_SERVER_NAME_OFFSET_IN_NT_CHALLENGE_RESPONSE - NTLM_V2_SERVER_NAME_RESERVED_LENGTH_IN_NT_CHALLENGE_RESPONSE); } // retrieve the time of client ICollection<AV_PAIR> targetInfo = NlmpUtility.BytesGetAvPairCollection(this.challenge.Payload.TargetInfo); // retrieve the time authenticateInformation.ClientTime = NlmpUtility.GetTime(targetInfo); // if server did not response the timestamp, use the client time stamp if (!NlmpUtility.AvPairContains(targetInfo, AV_PAIR_IDs.MsvAvTimestamp) && authenticateInformation.Version == NlmpVersion.v2) { authenticateInformation.ClientTime = BitConverter.ToUInt64( ArrayUtility.SubArray<byte>(authenticatePacket.Payload.NtChallengeResponse, NTLM_V2_TIME_STAMP_OFFSET_IN_NT_CHALLENGE_RESPONSE, TIME_STAMP_LENGTH), 0); } return authenticateInformation; }
/// <summary> /// valid the authenticate message /// </summary> /// <param name="authenticate"> /// the authenticate packet of client, contains the challenge response calculated by client /// </param> /// <param name="expectedNtChallengeResponse">the nt challenge response calculated by server</param> /// <param name="expectedLmChallengeResponse">the lm challenge response calculated by server</param> private static void ValidAuthenticateMessage( NlmpAuthenticatePacket authenticate, byte[] expectedNtChallengeResponse, byte[] expectedLmChallengeResponse) { if (authenticate.Payload.NtChallengeResponseFields.Len == 0 || !ArrayUtility.CompareArrays<byte>(expectedNtChallengeResponse, authenticate.Payload.NtChallengeResponse)) { if (authenticate.Payload.LmChallengeResponseFields.Len == 0 || !ArrayUtility.CompareArrays<byte>(expectedLmChallengeResponse, authenticate.Payload.LmChallengeResponse)) { throw new InvalidOperationException("INVALID message error"); } } }
/// <summary> /// this function create an authenticate packet. /// after client received the challenge packet from server, client create an authenticate packet to server. /// the authenticate packet contains the authentication information of user that is generated by the credential /// of user. /// this function does not set the mic field of packet. it must be set manually if need. /// </summary> /// <param name="negotiateFlags">this flags indicates the capabilities that client supports.</param> /// <param name="version"> /// This structure is used for debugging purposes only. In normal (non-debugging) protocol messages, it is /// ignored and does not affect the NTLM message processing. /// </param> /// <param name="lmChallengeResponse"> /// An LM_RESPONSE or LMv2_RESPONSE structure that contains the computed LM response to the challenge. If /// NTLM v2 authentication is configured, LmChallengeResponse MUST be an LMv2_RESPONSE structure. Otherwise, /// it MUST be an LM_RESPONSE structure. /// </param> /// <param name="ntChallengeResponse"> /// An NTLM_RESPONSE or NTLMv2_RESPONSE structure that contains the computed NT response to the challenge. /// If NTLM v2 authentication is configured, NtChallengeResponse MUST be an NTLMv2_RESPONSE. Otherwise, it /// MUST be an NTLM_RESPONSE structure. /// </param> /// <param name="domainName"> /// The domain or computer name hosting the user account. DomainName MUST be encoded in the negotiated /// character set. This param can not be null. /// </param> /// <param name="userName"> /// The name of the user to be authenticated. UserName MUST be encoded in the negotiated character set. This /// param can not be null. /// </param> /// <param name="workstation"> /// The name of the computer to which the user is logged on. Workstation MUST be encoded in the negotiated /// character set. This param can not be null. /// </param> /// <param name="encryptedRandomSessionKey"> /// The client's encrypted random session key. This param can be null. /// </param> /// <returns>the authenticate packet</returns> /// <exception cref="System.ArgumentNullException"> /// when LM is using, the ntChallengeResponse should be null! /// </exception> public NlmpAuthenticatePacket CreateAuthenticatePacket( NegotiateTypes negotiateFlags, VERSION version, byte[] lmChallengeResponse, byte[] ntChallengeResponse, string domainName, string userName, string workstation, byte[] encryptedRandomSessionKey ) { #region Parameter check if (domainName == null) { throw new ArgumentNullException("domainName"); } if (userName == null) { throw new ArgumentNullException("userName"); } if (workstation == null) { throw new ArgumentNullException("workstation"); } if (NlmpUtility.IsLm(negotiateFlags) && ntChallengeResponse != null) { throw new ArgumentException( "when LM is using, the ntChallengeResponse should be null!", "ntChallengeResponse"); } #endregion NlmpAuthenticatePacket packet = new NlmpAuthenticatePacket(); // update flags packet.SetNegotiateFlags(negotiateFlags); // if version required, update version if (NlmpUtility.IsVersionRequired(negotiateFlags)) { packet.SetVersion(version); } // update domain name packet.SetDomainName(domainName); // update user name packet.SetUserName(userName); // if version required, update workstation if (NlmpUtility.IsVersionRequired(negotiateFlags)) { packet.SetWorkstation(workstation); } // update lmChallengeResponse packet.SetLmChallengeResponse(lmChallengeResponse); // update ntChallengeResponse packet.SetNtChallengeResponse(ntChallengeResponse); // update encryptedRandomSessionKey packet.SetEncryptedRandomSessionKey(encryptedRandomSessionKey); return(packet); }
/// <summary> /// Verify the authenticate packet locally /// </summary> /// <param name="authenticatePacket">actual authenticate packet</param> /// <param name="authenticateInformation">expected authenticate information</param> /// <param name="exportedSessionKey">exported session key</param> /// <returns></returns> private ClientAuthenticateInfomation VerifyAuthenticatePacketLocally( NlmpAuthenticatePacket authenticatePacket, ClientAuthenticateInfomation authenticateInformation, out byte[] exportedSessionKey) { // valid user name if (authenticateInformation.UserName.ToUpper() != this.nlmpServer.Context.ClientCredential.AccountName.ToUpper()) { throw new InvalidOperationException( "the user name is invalid!" + " the user name retrieved form authenticate packet is not equal to the context."); } // calc the basekeys byte[] responseKeyLm; byte[] expectedNtChallengeResponse; byte[] expectedLmChallengeResponse; byte[] sessionBaseKey; byte[] keyExchangeKey; CalculateBaseKeys( authenticateInformation.ClientChallenge, this.systemTime, authenticateInformation.ServerName, authenticateInformation.DomainName, authenticateInformation.UserName, this.nlmpServer.Context.ClientCredential.Password, out responseKeyLm, out expectedNtChallengeResponse, out expectedLmChallengeResponse, out sessionBaseKey, out keyExchangeKey); // valid message ValidAuthenticateMessage(authenticatePacket, expectedNtChallengeResponse, expectedLmChallengeResponse); // generate keys. if (NlmpUtility.IsKeyExch(this.nlmpServer.Context.NegFlg)) { exportedSessionKey = NlmpUtility.RC4( keyExchangeKey, authenticatePacket.Payload.EncryptedRandomSessionKey); } else { exportedSessionKey = keyExchangeKey; } // validate mic byte[] messageMic = authenticatePacket.Payload.MIC; byte[] zeroMic = new byte[16]; if (messageMic != null && !ArrayUtility.CompareArrays <byte>(messageMic, zeroMic)) { AUTHENTICATE_MESSAGE payload = authenticatePacket.Payload; payload.MIC = zeroMic; authenticatePacket.Payload = payload; byte[] mic = NlmpUtility.GetMic(exportedSessionKey, this.negotiate, this.challenge, this.authenticate); if (!ArrayUtility.CompareArrays <byte>(messageMic, mic)) { throw new InvalidOperationException("mic of authenticate packet is invalid"); } } return(authenticateInformation); }
/// <summary> /// initialize the mic of challenge packet /// </summary> /// <param name="exportedSessionKey">the exported session key</param> /// <param name="targetInfo">the target info contains av pairs.</param> /// <param name="authenticatePacket">the authenticate packet</param> /// <param name="challengePacket">the challenge packet</param> private void InitializeChallengeMIC( byte[] exportedSessionKey, ICollection<AV_PAIR> targetInfo, NlmpAuthenticatePacket authenticatePacket, NlmpChallengePacket challengePacket ) { if (NlmpUtility.AvPairContains(targetInfo, AV_PAIR_IDs.MsvAvTimestamp)) { // update mic with security algorithm byte[] mic = null; // if connectionless, this.negotiate is null. mic = NlmpUtility.GetMic(exportedSessionKey, this.negotiate, challengePacket, authenticatePacket); // get payload of packet AUTHENTICATE_MESSAGE payload = authenticatePacket.Payload; // update mic to payload payload.MIC = mic; // update the meaningful payload to packet authenticatePacket.Payload = payload; } }