/// <summary> /// Check if the packet needs to be encrypted but actually not encrypted. /// </summary> private void CheckIfNeedEncrypt(Smb2SinglePacket packet, ulong sessionId = 0) { if (!CheckEncrypt) { return; } var realSessionId = sessionId == 0 ? packet.Header.SessionId : sessionId; var cryptoInfo = cryptoInfoTable.ContainsKey(realSessionId) ? cryptoInfoTable[realSessionId] : null; if (cryptoInfo != null) { if (cryptoInfo.EnableSessionEncryption || (cryptoInfo.EnableTreeEncryption.Contains(packet.Header.TreeId) && packet.Header.Command != Smb2Command.TREE_CONNECT)) { throw new Exception($"The packet should be encrypted: \"{packet.ToString()}\""); } } }
private void TryVerifySignature(Smb2SinglePacket singlePacket, ulong sessionId, byte[] messageBytes) { // [MS-SMB2] 3.2.5.1.3 //If the MessageId is 0xFFFFFFFFFFFFFFFF, no verification is necessary. if (singlePacket.Header.MessageId == 0xFFFFFFFFFFFFFFFF) { return; } Smb2CryptoInfo cryptoInfo = null; //[MS-SMB2] 3.2.5.1.3 //If the SMB2 header of the response has SMB2_FLAGS_SIGNED set in the Flags field, the client MUST verify the signature if (singlePacket.Header.Flags.HasFlag(Packet_Header_Flags_Values.FLAGS_SIGNED)) { //This is for the session binding success situation. //After the session setup success, the cryptoInfoTable will be updated with new signingkey //The new signingkey is not in the cryptoInfoTable now. Cannot verify the signature before the table is updated. if (cryptoInfoTable.TryGetValue(sessionId, out cryptoInfo)) { if (!VerifySignature(singlePacket, cryptoInfo, messageBytes)) { //If signature verification fails, the client MUST discard the received message and do no further processing for it. //The client MAY also choose to disconnect the connection. //Throw exception here. throw new InvalidOperationException("Incorrect signed packet: " + singlePacket.ToString()); } } } }
/// <summary> /// Verify the signature of a Smb2SinglePacket /// </summary> /// <param name="packet">The packet to be verified</param> /// <param name="cryptoInfo">The cryptoInfo of smb2client</param> /// <returns>True when signature verification succeeds and false when fails</returns> private bool VerifySignature(Smb2SinglePacket packet, Smb2CryptoInfo cryptoInfo, byte[] messageBytes) { if (cryptoInfo.DisableVerifySignature) { // Skip the verification. return(true); } try { if (IsErrorPacket(packet.Header)) { packet = packet.Error; } byte[] bytesToCompute = messageBytes; // Zero out the 16-byte signature field in the SMB2 Header of the incoming message. Array.Clear(bytesToCompute, System.Runtime.InteropServices.Marshal.SizeOf(packet.Header) - Smb2Consts.SignatureSize, Smb2Consts.SignatureSize); //Compute the message with signing key byte[] computedSignature = null; if (Smb2Utility.IsSmb3xFamily(cryptoInfo.Dialect)) { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect belongs to the SMB 3.x dialect family, //the receiver MUST compute a 16-byte hash by using AES-128-CMAC over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The AES-128-CMAC is specified in [RFC4493]. //TD has mentioned to use Session.SigningKey for SESSION_SETUP Response and Channel.SigningKey for other responses //In the current SDK, the SigningKey is the Channel.SigningKey computedSignature = AesCmac128.ComputeHash(cryptoInfo.SigningKey, bytesToCompute); } else { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect is "2.002" or "2.100", the receiver MUST compute a 32-byte hash by using HMAC-SHA256 over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The HMAC-SHA256 hash is specified in [FIPS180-2] and [RFC2104]. HMACSHA256 hmacSha = new HMACSHA256(cryptoInfo.SigningKey); computedSignature = hmacSha.ComputeHash(bytesToCompute); } //[MS-SMB2] 3.1.5.1 //If the first 16 bytes (the high-order portion) of the computed signature from step 3 or step 4 matches the saved signature from step 1, the message is signed correctly // compare the first 16 bytes of the originalSignature and computedSignature return(packet.Header.Signature.SequenceEqual(computedSignature.Take(Smb2Consts.SignatureSize))); } catch (Exception ex) { throw new Exception("Error happened during signature verification of packet: " + packet.ToString() + ". Exception message: " + ex.Message); } }
/// <summary> /// Verify the signature of a Smb2SinglePacket /// </summary> /// <param name="packet">The packet to be verified</param> /// <param name="cryptoInfo">The cryptoInfo of smb2client</param> /// <returns>True when signature verification succeeds and false when fails</returns> private bool VerifySignature(Smb2SinglePacket packet, Smb2CryptoInfo cryptoInfo, byte[] messageBytes) { if (cryptoInfo.DisableVerifySignature) { // Skip the verification. return true; } try { if (IsErrorPacket(packet.Header)) { packet = packet.Error; } byte[] bytesToCompute = messageBytes; // Zero out the 16-byte signature field in the SMB2 Header of the incoming message. Array.Clear(bytesToCompute, System.Runtime.InteropServices.Marshal.SizeOf(packet.Header) - Smb2Consts.SignatureSize, Smb2Consts.SignatureSize); //Compute the message with signing key byte[] computedSignature = null; if (Smb2Utility.IsSmb3xFamily(cryptoInfo.Dialect)) { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect belongs to the SMB 3.x dialect family, //the receiver MUST compute a 16-byte hash by using AES-128-CMAC over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The AES-128-CMAC is specified in [RFC4493]. //TD has mentioned to use Session.SigningKey for SESSION_SETUP Response and Channel.SigningKey for other responses //In the current SDK, the SigningKey is the Channel.SigningKey computedSignature = AesCmac128.ComputeHash(cryptoInfo.SigningKey, bytesToCompute); } else { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect is "2.002" or "2.100", the receiver MUST compute a 32-byte hash by using HMAC-SHA256 over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The HMAC-SHA256 hash is specified in [FIPS180-2] and [RFC2104]. HMACSHA256 hmacSha = new HMACSHA256(cryptoInfo.SigningKey); computedSignature = hmacSha.ComputeHash(bytesToCompute); } //[MS-SMB2] 3.1.5.1 //If the first 16 bytes (the high-order portion) of the computed signature from step 3 or step 4 matches the saved signature from step 1, the message is signed correctly // compare the first 16 bytes of the originalSignature and computedSignature return packet.Header.Signature.SequenceEqual(computedSignature.Take(Smb2Consts.SignatureSize)); } catch (Exception ex) { throw new Exception("Error happened during signature verification of packet: " + packet.ToString() + ". Exception message: " + ex.Message); } }
/// <summary> /// Try to verify the signature of a singlePacket /// </summary> /// <param name="singlePacket">The singlepacket to verified</param> /// <param name="sessionId">The sessionId to retrieve the cryptoinfo of the session.</param> private void TryVerifySignature(Smb2SinglePacket singlePacket, ulong sessionId, byte[] messageBytes) { // [MS-SMB2] 3.2.5.1.3 //If the MessageId is 0xFFFFFFFFFFFFFFFF, no verification is necessary. if (singlePacket.Header.MessageId == 0xFFFFFFFFFFFFFFFF) { return; } Smb2CryptoInfo cryptoInfo = null; //[MS-SMB2] 3.2.5.1.3 //If the SMB2 header of the response has SMB2_FLAGS_SIGNED set in the Flags field, the client MUST verify the signature if (singlePacket.Header.Flags.HasFlag(Packet_Header_Flags_Values.FLAGS_SIGNED)) { //This is for the session binding success situation. //After the session setup success, the cryptoInfoTable will be updated with new signingkey //The new signingkey is not in the cryptoInfoTable now. Cannot verify the signature before the table is updated. if (singlePacket.Header.Command == Smb2Command.SESSION_SETUP && singlePacket.Header.Status == Smb2Status.STATUS_SUCCESS) { //skip return; } if (cryptoInfoTable.TryGetValue(sessionId, out cryptoInfo)) { if (!VerifySignature(singlePacket, cryptoInfo, messageBytes)) { //If signature verification fails, the client MUST discard the received message and do no further processing for it. //The client MAY also choose to disconnect the connection. //Throw exception here. throw new InvalidOperationException("Incorrect signed packet: " + singlePacket.ToString()); } } } return; }
/// <summary> /// Check if the packet needs to be encrypted but actually not encrypted. /// </summary> private void CheckIfNeedEncrypt(Smb2SinglePacket packet, ulong sessionId = 0) { if (!CheckEncrypt) { return; } var realSessionId = sessionId == 0 ? packet.Header.SessionId : sessionId; var cryptoInfo = cryptoInfoTable.ContainsKey(realSessionId) ? cryptoInfoTable[realSessionId] : null; if (cryptoInfo != null) { if (cryptoInfo.EnableSessionEncryption) { // MS-SMB2 3.3.4.1.4 Encrypting the Message // If Connection.Dialect belongs to the SMB 3.x dialect family and Connection.ClientCapabilities includes the SMB2_GLOBAL_CAP_ENCRYPTION bit, the server MUST encrypt the message before sending, if IsEncryptionSupported is TRUE and any of the following conditions are satisfied: // If the message being sent is any response to a client request for which Request.IsEncrypted is TRUE. // If Session.EncryptData is TRUE and the response being sent is not SMB2_NEGOTIATE or SMB2 SESSION_SETUP. // If Session.EncryptData is FALSE, the response being sent is not SMB2_NEGOTIATE or SMB2 SESSION_SETUP or SMB2 TREE_CONNECT, and Share.EncryptData for the share associated with the TreeId in the SMB2 header of the response is TRUE. // The server MUST encrypt the message as specified in section 3.1.4.3, before sending it to the client. // above description means SMB2_NEGOTIATE or SMB2 SESSION_SETUP never encrypts the message. if (packet.Header.Command == Smb2Command.SESSION_SETUP && (packet is Smb2SessionSetupResponsePacket)) { return; } } if (cryptoInfo.EnableSessionEncryption) { throw new Exception($"The packet should be encrypted for session: \"{packet.ToString()}\""); } if ((cryptoInfo.EnableTreeEncryption.Contains(packet.Header.TreeId) && packet.Header.Command != Smb2Command.TREE_CONNECT)) { throw new Exception($"The packet should be encrypted for tree: \"{packet.ToString()}\""); } } }
/// <summary> /// Decode the message except length field which may exist if transport is tcp /// </summary> /// <param name="messageBytes">The received packet</param> /// <param name="role">The role of this decoder, client or server</param> /// <param name="ignoreCompoundFlag">indicate whether decode the packet as a single packet or a compound packet /// when compound flag is set</param> /// <param name="realSessionId">The real sessionId for this packet</param> /// <param name="realTreeId">The real treeId for this packet</param> /// <param name="consumedLength">[OUT]The consumed length of the message</param> /// <param name="expectedLength">[OUT]The expected length</param> /// <returns>A Smb2Packet</returns> public Smb2Packet DecodeCompletePacket( byte[] messageBytes, Smb2Role role, bool ignoreCompoundFlag, ulong realSessionId, uint realTreeId, out int consumedLength, out int expectedLength ) { //protocol version is of 4 bytes len byte[] protocolVersion = new byte[sizeof(uint)]; Array.Copy(messageBytes, 0, protocolVersion, 0, protocolVersion.Length); SmbVersion version = DecodeVersion(protocolVersion); if (version == SmbVersion.Version1) { // SMB Negotiate packet return(DecodeSmbPacket(messageBytes, role, out consumedLength, out expectedLength)); } else if (version == SmbVersion.Version2Encrypted) { // SMB2 encrypted packet return(DecodeEncryptedSmb2Packet( messageBytes, role, ignoreCompoundFlag, realSessionId, realTreeId, out consumedLength, out expectedLength )); } else { // SMB2 packet not encrypted Smb2Packet decodedPacket = DecodeSmb2Packet( messageBytes, role, ignoreCompoundFlag, realSessionId, realTreeId, out consumedLength, out expectedLength ); //For single packet signature verification if (decodedPacket is Smb2SinglePacket) { //verify signature of a single packet Smb2SinglePacket singlePacket = decodedPacket as Smb2SinglePacket; if (!TryVerifySignature(singlePacket, singlePacket.Header.SessionId)) { throw new Exception("Error happened during signature verification of single packet: " + singlePacket.ToString()); } } else if (decodedPacket is Smb2CompoundPacket)//For Compound packet signature verification { //verify signature of the compound packet if (!TryVerifySignature(decodedPacket as Smb2CompoundPacket)) { throw new Exception("Error happened during signature verification of compound packet: " + decodedPacket.ToString()); } } return(decodedPacket); } }