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) { if (cryptoInfo.DisableVerifySignature) { // Skip the verification. return(true); } try { if (IsErrorPacket(packet.Header)) { packet = packet.Error; } //save the 16-byte signature from the Signature field in the SMB2 Header byte[] originalSignature = packet.Header.Signature; //zero out the 16-byte signature field in the SMB2 Header of the incoming message. packet.Header.Signature = new byte[Smb2Consts.SignatureSize]; byte[] bytesToCompute = packet.ToBytes(); //Compute the message with signing key byte[] computedSignature = new byte[] { }; 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); } packet.Header.Signature = originalSignature; //[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(originalSignature.SequenceEqual(computedSignature.Take(16))); } catch (Exception ex) { throw new Exception("Error happened during signature verification of packet: " + packet.ToString() + ". Exception message: " + ex.Message); } }
/// <summary> /// Generate encrypt/decrypt and signing keys according to the smb2 dialect, cryptographicKey(session key), PreauthIntegrityHashValue and existing cryptInfo /// If the param cryptoInfo is not null, then the param cipherId will be ignored. /// The binding session should use the same cipherId as the master session. /// </summary> /// <param name="dialect">Smb2 dialect</param> /// <param name="cryptographicKey">The key to derive new encrypt/decrypt/signing key </param> /// <param name="enableSigning">True if signing is enabled, otherwise false</param> /// <param name="enableEncryption">True if encrypt is enabled, otherwise false</param> /// <param name="cryptoInfo">The existing cryptoInfo. This is for the binding session</param> /// <param name="preauthIntegrityHashValue">The preauthentication integrity hash value, for smb dialect 311 only </param> /// <param name="cipherId">The ID of the cipher that was negotiated for this connection</param> public Smb2CryptoInfo( DialectRevision dialect, byte[] cryptographicKey, bool enableSigning, bool enableEncryption, bool disableVerifySignature, Smb2CryptoInfo cryptoInfo = null, byte[] preauthIntegrityHashValue = null, EncryptionAlgorithm cipherId = EncryptionAlgorithm.ENCRYPTION_AES128_CCM) { if (dialect >= DialectRevision.Smb311 && dialect != DialectRevision.Smb2Unknown && preauthIntegrityHashValue == null) { if (cryptoInfo == null && enableEncryption) // For the alternative channel, the cryptoinfo should be the same as the main channel. { throw new ArgumentNullException("For SMB 3.11, null preauthIntegrityHashValue is not allowed when encryption is enabled."); } if (enableSigning) // For the alternative channel, the signingkey should be recalculated. { throw new ArgumentNullException("For SMB 3.11, null preauthIntegrityHashValue is not allowed when signing is enabled."); } } DisableVerifySignature = disableVerifySignature; Dialect = dialect; SessionKey = cryptographicKey; FullSessionKey = cryptographicKey; // TD indicates that when signing the message the protocol uses // the first 16 bytes of the cryptographic key for this authenticated context. // If the cryptographic key is less than 16 bytes, it is right-padded with zero bytes. if (SessionKey.Length < 16) { SessionKey = SessionKey.Concat(new byte[16 - SessionKey.Length]).ToArray(); } else if (SessionKey.Length > 16) { SessionKey = SessionKey.Take(16).ToArray(); } if (Dialect == DialectRevision.Smb2002 || Dialect == DialectRevision.Smb21) { SigningKey = SessionKey; } else if (Dialect == DialectRevision.Smb30 || Dialect == DialectRevision.Smb302) { SigningKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( SessionKey, Encoding.ASCII.GetBytes("SMB2AESCMAC\0"), Encoding.ASCII.GetBytes("SmbSign\0"), 128); } else // dialect == SMB311 { SigningKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( SessionKey, Encoding.ASCII.GetBytes("SMBSigningKey\0"), preauthIntegrityHashValue, 128); } if (cryptoInfo == null) { if (dialect >= DialectRevision.Smb311) { CipherId = cipherId; } else { // for pre SMB 3.11 dialects, use AES-128-CCM for encryption CipherId = EncryptionAlgorithm.ENCRYPTION_AES128_CCM; } /** * If Connection.CipherId is AES-128-CCM or AES-128-GCM, 'L' is initialized to 128. * If Connection.CipherId is AES-256-CCM or AES256-GCM, 'L' value is initialized to 256. * * If Connection.Dialect is “3.1.1” and Connection.CipherId is AES-256(CCM or GCM), * Session.FullSessionKey as the key derivation key. Otherwise, Session.SessionKey as the key * derivation key **/ if (CipherId == EncryptionAlgorithm.ENCRYPTION_AES128_CCM || CipherId == EncryptionAlgorithm.ENCRYPTION_AES128_GCM) { LValue = 128; } else { LValue = 256; } if (dialect == DialectRevision.Smb311 && (CipherId == EncryptionAlgorithm.ENCRYPTION_AES256_CCM || CipherId == EncryptionAlgorithm.ENCRYPTION_AES256_GCM)) { EncryptionKey = FullSessionKey; } else { EncryptionKey = SessionKey; } ServerInKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( EncryptionKey, // If Connection.Dialect is "3.1.1", the case-sensitive ASCII string "SMBC2SCipherKey" as the label; // otherwise, the case-sensitive ASCII string "SMB2AESCCM" as the label. Dialect == DialectRevision.Smb311 ? Encoding.ASCII.GetBytes("SMBC2SCipherKey\0") : Encoding.ASCII.GetBytes("SMB2AESCCM\0"), // If Connection.Dialect is "3.1.1", Session.PreauthIntegrityHashValue as the context; // otherwise, the case-sensitive ASCII string "ServerIn " as context for the algorithm (note the blank space at the end). Dialect == DialectRevision.Smb311 ? preauthIntegrityHashValue : Encoding.ASCII.GetBytes("ServerIn \0"), LValue); ServerOutKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( EncryptionKey, // If Connection.Dialect is "3.100", the case-sensitive ASCII string "SMBS2CCipherKey" as the label; // otherwise, the case-sensitive ASCII string "SMB2AESCCM" as the label. Dialect == DialectRevision.Smb311 ? Encoding.ASCII.GetBytes("SMBS2CCipherKey\0") : Encoding.ASCII.GetBytes("SMB2AESCCM\0"), // If Connection.Dialect is "3.100", Session.PreauthIntegrityHashValue as the context; // otherwise, the case-sensitive ASCII string "ServerOut" as context for the algorithm. Dialect == DialectRevision.Smb311 ? preauthIntegrityHashValue : Encoding.ASCII.GetBytes("ServerOut\0"), LValue); ApplicationKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( SessionKey, // If Connection.Dialect is "3.1.1", the case-sensitive ASCII string "SMBAppKey" as the label; // otherwise, the case-sensitive ASCII string "SMB2APP" as the label. Dialect == DialectRevision.Smb311 ? Encoding.ASCII.GetBytes("SMBAppKey\0") : Encoding.ASCII.GetBytes("SMB2APP\0"), // If Connection.Dialect is "3.1.1", Session.PreauthIntegrityHashValue as the context; // otherwise, the case-sensitive ASCII string "SmbRpc" as context for the algorithm. Dialect == DialectRevision.Smb311 ? preauthIntegrityHashValue : Encoding.ASCII.GetBytes("SmbRpc\0"), 128); } else { // Reuse encryption/decryption key that is generated in first channel's session setup // when send encrypted request in alternative channels CipherId = cryptoInfo.CipherId; ServerInKey = cryptoInfo.ServerInKey; ServerOutKey = cryptoInfo.ServerOutKey; ApplicationKey = cryptoInfo.ApplicationKey; } EnableSessionSigning = enableSigning; EnableSessionEncryption = enableEncryption; }
/// <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> /// Generate encrypt/decrypt and signing keys according to the smb2 dialect, cryptographicKey(session key), PreauthIntegrityHashValue and existing cryptInfo /// If the param cryptoInfo is not null, then the param cipherId will be ignored. /// The binding session should use the same cipherId as the master session. /// </summary> /// <param name="dialect">Smb2 dialect</param> /// <param name="cryptographicKey">The key to derive new encrypt/decrypt/signing key </param> /// <param name="enableSigning">True if signing is enabled, otherwise false</param> /// <param name="enableEncryption">True if encrypt is enabled, otherwise false</param> /// <param name="cryptoInfo">The existing cryptoInfo. This is for the binding session</param> /// <param name="preauthIntegrityHashValue">The preauthentication integrity hash value, for smb dialect 311 only </param> /// <param name="cipherId">The ID of the cipher that was negotiated for this connection</param> public Smb2CryptoInfo( DialectRevision dialect, byte[] cryptographicKey, bool enableSigning, bool enableEncryption, Smb2CryptoInfo cryptoInfo = null, byte[] preauthIntegrityHashValue = null, EncryptionAlgorithm cipherId = EncryptionAlgorithm.ENCRYPTION_AES128_CCM) { if (dialect >= DialectRevision.Smb311 && dialect != DialectRevision.Smb2Unknown && preauthIntegrityHashValue == null) { if (cryptoInfo == null && enableEncryption) // For the alternative channel, the cryptoinfo should be the same as the main channel. { throw new ArgumentNullException("For SMB 3.11, null preauthIntegrityHashValue is not allowed when encryption is enabled."); } if(enableSigning) // For the alternative channel, the signingkey should be recalculated. { throw new ArgumentNullException("For SMB 3.11, null preauthIntegrityHashValue is not allowed when signing is enabled."); } } Dialect = dialect; SessionKey = cryptographicKey; // TD indicates that when signing the message the protocol uses // the first 16 bytes of the cryptographic key for this authenticated context. // If the cryptographic key is less than 16 bytes, it is right-padded with zero bytes. if (SessionKey.Length < 16) SessionKey = SessionKey.Concat(new byte[16 - SessionKey.Length]).ToArray(); else if (SessionKey.Length > 16) SessionKey = SessionKey.Take(16).ToArray(); if (Dialect == DialectRevision.Smb2002 || Dialect == DialectRevision.Smb21) SigningKey = SessionKey; else if (Dialect == DialectRevision.Smb30 || Dialect == DialectRevision.Smb302) SigningKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( SessionKey, Encoding.ASCII.GetBytes("SMB2AESCMAC\0"), Encoding.ASCII.GetBytes("SmbSign\0"), 128); else // dialect == SMB311 { SigningKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( SessionKey, Encoding.ASCII.GetBytes("SMBSigningKey\0"), preauthIntegrityHashValue, 128); } if (cryptoInfo == null) { if (dialect >= DialectRevision.Smb311) { CipherId = cipherId; } else { // for pre SMB 3.11 dialects, use AES-128-CCM for encryption CipherId = EncryptionAlgorithm.ENCRYPTION_AES128_CCM; } ServerInKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( SessionKey, // If Connection.Dialect is "3.1.1", the case-sensitive ASCII string "SMBC2SCipherKey" as the label; // otherwise, the case-sensitive ASCII string "SMB2AESCCM" as the label. Dialect == DialectRevision.Smb311 ? Encoding.ASCII.GetBytes("SMBC2SCipherKey\0") : Encoding.ASCII.GetBytes("SMB2AESCCM\0"), // If Connection.Dialect is "3.1.1", Session.PreauthIntegrityHashValue as the context; // otherwise, the case-sensitive ASCII string "ServerIn " as context for the algorithm (note the blank space at the end). Dialect == DialectRevision.Smb311 ? preauthIntegrityHashValue : Encoding.ASCII.GetBytes("ServerIn \0"), 128); ServerOutKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( SessionKey, // If Connection.Dialect is "3.100", the case-sensitive ASCII string "SMBS2CCipherKey" as the label; // otherwise, the case-sensitive ASCII string "SMB2AESCCM" as the label. Dialect == DialectRevision.Smb311 ? Encoding.ASCII.GetBytes("SMBS2CCipherKey\0") : Encoding.ASCII.GetBytes("SMB2AESCCM\0"), // If Connection.Dialect is "3.100", Session.PreauthIntegrityHashValue as the context; // otherwise, the case-sensitive ASCII string "ServerOut" as context for the algorithm. Dialect == DialectRevision.Smb311 ? preauthIntegrityHashValue : Encoding.ASCII.GetBytes("ServerOut\0"), 128); ApplicationKey = SP8001008KeyDerivation.CounterModeHmacSha256KeyDerive( SessionKey, // If Connection.Dialect is "3.1.1", the case-sensitive ASCII string "SMBAppKey" as the label; // otherwise, the case-sensitive ASCII string "SMB2APP" as the label. Dialect == DialectRevision.Smb311 ? Encoding.ASCII.GetBytes("SMBAppKey\0") : Encoding.ASCII.GetBytes("SMB2APP\0"), // If Connection.Dialect is "3.1.1", Session.PreauthIntegrityHashValue as the context; // otherwise, the case-sensitive ASCII string "SmbRpc" as context for the algorithm. Dialect == DialectRevision.Smb311 ? preauthIntegrityHashValue : Encoding.ASCII.GetBytes("SmbRpc\0"), 128); } else { // Reuse encryption/decryption key that is generated in first channel's session setup // when send encrypted request in alternative channels CipherId = cryptoInfo.CipherId; ServerInKey = cryptoInfo.ServerInKey; ServerOutKey = cryptoInfo.ServerOutKey; ApplicationKey = cryptoInfo.ApplicationKey; } EnableSessionSigning = enableSigning; EnableSessionEncryption = enableEncryption; }
/// <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 if (cryptoInfo.SigningId == SigningAlgorithm.AES_GMAC) { var nonce = Smb2Utility.ComputeNonce(packet, this.decodeRole); var(_ciphertext, tag) = AesGmac.ComputeHash(cryptoInfo.SigningKey, nonce, bytesToCompute); computedSignature = tag; } else { 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); } }