/// <summary> /// Construct DIGEST_VALIDATION_RESP structure from Dpsp pass-through validation data /// returned from domain controller. /// </summary> /// <param name="validationInfo">validation information /// returned from domain controller in Dpsp pass-through authentication /// </param> /// <returns>DIGEST_VALIDATION_RESP structure</returns> /// <exception cref="ArgumentException"> /// throw when validationInfo input is invalid. /// </exception> public static DIGEST_VALIDATION_RESP ConvertDataToDigestValidationResponse(_NETLOGON_VALIDATION validationInfo) { if (validationInfo.ValidationGeneric2 == null) { throw new ArgumentException( "ValidationGeneric2 array should not be null", "validationInfo"); } if (validationInfo.ValidationGeneric2[0].ValidationData == null) { throw new ArgumentException( "ValidationData byte array in ValidationGeneric2 should not be null", "validationInfo"); } DIGEST_VALIDATION_RESP digestValidationResp = new DIGEST_VALIDATION_RESP(); byte[] validationData = validationInfo.ValidationGeneric2[0].ValidationData; using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(validationData))) { digestValidationResp.MessageType = (MessageType_Values)binaryReader.ReadInt32(); digestValidationResp.Version = (Version_Values)binaryReader.ReadInt16(); digestValidationResp.Pad2 = (Pad2_Values)binaryReader.ReadInt16(); digestValidationResp.Status = (uint)binaryReader.ReadInt32(); digestValidationResp.SessionKeyLength = (ushort)binaryReader.ReadInt16(); digestValidationResp.Pad3 = (Pad3_Values)binaryReader.ReadInt16(); digestValidationResp.AuthDataSize = (uint)binaryReader.ReadInt32(); digestValidationResp.AcctNameSize = (ushort)binaryReader.ReadInt16(); digestValidationResp.Reserved1 = (Reserved1_Values)binaryReader.ReadInt16(); digestValidationResp.MessageSize = (uint)binaryReader.ReadInt32(); digestValidationResp.Reserved3 = (Reserved3_Values)binaryReader.ReadInt32(); digestValidationResp.SessionKey = binaryReader.ReadBytes(DIGEST_SESSION_KEY_LENGTH); digestValidationResp.SessionKey_NULL_terminator = (SessionKey_NULL_terminator_Values)binaryReader.ReadByte(); digestValidationResp.Pad4 = new Pad4_Values[DIGEST_VALIDATION_RESPONSE_PAD4_LENGTH]; byte[] pad4Data = binaryReader.ReadBytes(DIGEST_VALIDATION_RESPONSE_PAD4_LENGTH); for (int i = 0; i < DIGEST_VALIDATION_RESPONSE_PAD4_LENGTH; i++) { digestValidationResp.Pad4[i] = (Pad4_Values)pad4Data[i]; } digestValidationResp.Pad1 = (Pad1_Values)binaryReader.ReadInt64(); digestValidationResp.AuthData = binaryReader.ReadBytes((int)digestValidationResp.AuthDataSize); digestValidationResp.AccountName = binaryReader.ReadBytes((int)digestValidationResp.AcctNameSize); } return(digestValidationResp); }
/// <summary> /// Send Digest request message to DC, and validate the credentials. /// </summary> /// <param name="isValidationSuccess">Indicates whether the validation from server will be success.</param> /// <param name="digestType">Indicates the DigestType field of the request.</param> /// <param name="algType">Indicates the AlgType field of the request.</param> /// <param name="ignoredFields">It indicates the fields that should be ignored by DC in DIGEST_VALIDATION_REQ.</param> /// <returns>Indicates the result status of DC response.</returns> public Status GenerateDigestRequest( Boolean isValidationSuccess, AccountInformation accountInfo, DigestType_Values digestType, AlgType_Values algType, IgnoredFields ignoredFields) { DIGEST_VALIDATION_REQ digestReq; _NETLOGON_LOGON_INFO_CLASS logonLevel = _NETLOGON_LOGON_INFO_CLASS.NetlogonGenericInformation; _NETLOGON_VALIDATION_INFO_CLASS validationLevel = _NETLOGON_VALIDATION_INFO_CLASS.NetlogonValidationGenericInfo2; //Compute the password GenerateCurrentCredentials(accountInfo); //Get Digest Request GetDigestRequest(digestType, algType, ignoredFields, out digestReq); //Create digest validation logon info _NETLOGON_LEVEL netlogonLevel = ApdsUtility.CreateDpspLogonInfo( NrpcParameterControlFlags.AllowLogonWithComputerAccount, digestReq); //Create Secure Channel EstablishSecureChannel(); //Client calls EncryptNetlogonLevel _NETLOGON_LEVEL encryptedLogonLevel = nrpcClient.EncryptNetlogonLevel( (_NETLOGON_LOGON_INFO_CLASS)logonLevel, netlogonLevel); //Client calls NetrLogonSamLogonEx _NETLOGON_VALIDATION?validationInfomation; byte?authoritative; NrpcNetrLogonSamLogonExtraFlags?extraFlags = NrpcNetrLogonSamLogonExtraFlags.None; result = nrpcClient.NetrLogonSamLogonEx( nrpcClient.Handle, sutComputerName, clientComputerName, logonLevel, encryptedLogonLevel, validationLevel, out validationInfomation, out authoritative, ref extraFlags); // Whether this method use pass-through mechanism or not. bool isPassThroughMethod = false; // //The Digest validation protocol SHOULD use the generic pass-through mechanism. //For generic pass-through, the LogonLevel is 4(NetlogonGenericInformation) as defined in [MS-NRPC] section 3.2.4.1. //So when the LogonLevel is 4, we can say that NRPC pass-through authentication method is used. // if ((int)logonLevel == 4) { isPassThroughMethod = true; } // //Verify MS-APDS requirment:MS-APDS_R5 // Site.CaptureRequirementIfIsTrue( isPassThroughMethod, 5, @"For domain support, authentication protocols MUST use an NRPC pass-through authentication ([MS-NRPC] section 3.2) method with parameters determined by the authentication protocol being used[to return the response structures to member server]."); // //Verify MS-APDS requirment:MS-APDS_R15 // Site.CaptureRequirementIfAreEqual <int>( 5, (int)validationLevel, 15, @"Digest response messages MUST be encoded as opaque blobs and transported by the generic pass-through capability of Netlogon."); // //Configured in PTFConfig file,default SHOULD to true and MAY to false. // string isR169Implemented = "true"; // //Check OS version // if (isWindows) { // //Verify MS-APDS requirment:MS-APDS_R100169 // Site.CaptureRequirementIfAreEqual <int>( 5, (int)validationLevel, 100169, @"In Windows, the Digest validation protocol uses the generic pass-through mechanism."); if (null == isR169Implemented) { Site.Properties.Add("R169Implemented", Boolean.TrueString); isR169Implemented = Boolean.TrueString; } } if (null != isR169Implemented) { bool implSigns = Boolean.Parse(isR169Implemented); bool isSatisfied = ((int)_NETLOGON_VALIDATION_INFO_CLASS.NetlogonValidationGenericInfo2 == 5); // //Verify MS-APDS requirment:MS-APDS_R169 // Site.CaptureRequirementIfAreEqual <Boolean>( implSigns, isSatisfied, 169, string.Format(@"The Digest validation protocol SHOULD use the generic pass-through mechanism. This requirement is {0} implemented.", implSigns ? "" : "not")); } byte[] buf; bool isDigestResStructure = true; DIGEST_VALIDATION_RESP digestValidationResponse = new DIGEST_VALIDATION_RESP(); // Judge whether the digest response is exist, validationData is response point. // if validationData equals 0, no response return from NRPC. if (validationInfomation.Value.ValidationGeneric2 == null) { isDigestResStructure = false; } else { // decrypt validation info buf = NrpcUtility.DecryptBuffer( (nrpcClient.Context.NegotiateFlags & NrpcNegotiateFlags.SupportsAESAndSHA2) == NrpcNegotiateFlags.SupportsAESAndSHA2, nrpcClient.Context.SessionKey, validationInfomation.Value.ValidationGeneric2[0].ValidationData); _NETLOGON_VALIDATION decryptValidationInfo = new _NETLOGON_VALIDATION(); decryptValidationInfo.ValidationGeneric2 = new _NETLOGON_VALIDATION_GENERIC_INFO2[1]; decryptValidationInfo.ValidationGeneric2[0].ValidationData = buf; digestValidationResponse = ApdsUtility.ConvertDataToDigestValidationResponse(decryptValidationInfo); VerifyMessageSyntaxDigestValidCredential(digestValidationResponse, validationLevel, (uint)buf.Length); } if (result != NtStatus.STATUS_SUCCESS) { // //Verify MS-APDS requirment:MS-APDS_R292 // Site.CaptureRequirementIfIsFalse( isDigestResStructure, 292, "[If unsuccessful]It[DC] MUST NOT send back the DIGEST_VALIDATION_RESP message."); VerifyMessageSyntxDigestInvalidResp((uint)result, digestValidationResponse.AuthDataSize); } return((Status)result); }
/// <summary> /// Send NTLM request message to DC for validation. /// </summary> /// <param name="logonLevel">Indicates the logon level.</param> /// <param name="accountInfo">Indicates whether this account is valid in DC validation.</param> /// <param name="isAccessCheckSuccess">Indicates whether the access checked success.</param> /// <param name="validationLevel">Indicates the validation level.</param> /// <returns>Indicates the result status of DC response.</returns> public Status NTLMLogon( _NETLOGON_LOGON_INFO_CLASS logonLevel, AccountInformation accountInfo, Boolean isAccessCheckSuccess, _NETLOGON_VALIDATION_INFO_CLASS validationLevel) { //Compute the password GenerateCurrentCredentials(accountInfo); //Create Secure Channel EstablishSecureChannel(); _NETLOGON_LEVEL netLogonLevel = new _NETLOGON_LEVEL(); if (logonLevel == _NETLOGON_LOGON_INFO_CLASS.NetlogonInteractiveInformation) { //Fill InteractiveInfo netLogonLevel = ApdsUtility.CreateNlmpInteractiveLogonInfo( NrpcParameterControlFlags.AllowLogonWithComputerAccount, sutTrustDomainName, currentUserName, currentPassword, sutComputerName); } else if (logonLevel == _NETLOGON_LOGON_INFO_CLASS.NetlogonNetworkInformation) { //Fill Netlogon_Network_Info if (accountInfo == AccountInformation.ManagedServiceAccount) { netLogonLevel = nrpcClient.CreateNetlogonLevel( _NETLOGON_LOGON_INFO_CLASS.NetlogonNetworkInformation, NrpcParameterControlFlags.AllowLogonWithComputerAccount, sutDomainName, // managed service account is created in the local domain currentUserName, currentPassword); } else { netLogonLevel = nrpcClient.CreateNetlogonLevel( _NETLOGON_LOGON_INFO_CLASS.NetlogonNetworkInformation, NrpcParameterControlFlags.AllowLogonWithComputerAccount, sutTrustDomainName, currentUserName, currentPassword); } } _NETLOGON_LEVEL encryptedLogonLevel = nrpcClient.EncryptNetlogonLevel( logonLevel, netLogonLevel); byte?authoritative; _NETLOGON_VALIDATION? validationInformation = new _NETLOGON_VALIDATION(); NrpcNetrLogonSamLogonExtraFlags?extraFlags = NrpcNetrLogonSamLogonExtraFlags.None; result = nrpcClient.NetrLogonSamLogonEx( nrpcClient.Handle, sutComputerName, clientComputerName, logonLevel, encryptedLogonLevel, validationLevel, out validationInformation, out authoritative, ref extraFlags); // Whether this method use pass-through mechanism or not. bool isPassThroughMethod = false; // //For NTLM pass-through, the LogonLevel is 1(NetlogonInteractiveInformation) or 2(NetlogonNetworkInformation) //based on Interactive Logono and Network Logon,as defined in [MS-APDS] section 3.1.5.1 and section 3.1.5.2. //So when the LogonLevel is 1 or 2, we can say that NRPC pass-through authentication method is used. // if ((int)logonLevel == 1 || (int)logonLevel == 2) { isPassThroughMethod = true; } // //Verify MS-APDS requirment:MS-APDS_R5 // Site.CaptureRequirementIfIsTrue( isPassThroughMethod, 5, @"For domain support, authentication protocols MUST use an NRPC pass-through authentication ([MS-NRPC] section 3.2) method with parameters determined by the authentication protocol being used[to return the response structures to member server]."); // //Verify MS-APDS requirment:MS-APDS_R118 //if isPassThroughMethod is true, this requirement can be verified. // Site.CaptureRequirementIfIsTrue( isPassThroughMethod, 118, @"NT LAN Manager (NTLM) interactive logon and network logon MUST receive the authentication response sequence by contacting the DC using an NRPC pass-through authentication method ."); if (result == NtStatus.STATUS_SUCCESS) { if (validationLevel == _NETLOGON_VALIDATION_INFO_CLASS.NetlogonValidationSamInfo4) { VerifyNTLMDataStructure(validationLevel, logonLevel); VerifyMessageSyntxNTLMValidV4Resp( validationInformation.Value.ValidationSam4[0].UserSessionKey.data[0].data, validationInformation.Value.ValidationSam4[0].EffectiveName.ToString()); } else if (validationLevel == _NETLOGON_VALIDATION_INFO_CLASS.NetlogonValidationSamInfo2) { VerifyNTLMDataStructure(validationLevel, logonLevel); } else if (validationLevel == _NETLOGON_VALIDATION_INFO_CLASS.NetlogonValidationSamInfo) { VerifyNTLMDataStructure(validationLevel, logonLevel); } } return((Status)result); }