Skip to content

Conversation

JaniruTEC
Copy link
Contributor

No description provided.

overheadhunter and others added 15 commits April 11, 2025 16:15
Changed initialization of "sessionGlobalId"
Changed type of "sessionGlobalId" to int
Changed "Global.sessionTable" to use "sessionId"
Added assertions
Matched dialect check closer to spec: Now uses "connection.dialect" and accepts any 3x dialect; note that in practice the dialect can only be "3.1.1" at this point
Added missing conditions/execution paths related to "Global.isMultiChannelCapable"
Externalized method "gssAuthenticate" and fixed control flow to it
Implemented reauthentication further
Changed the flow for supplying the singing key/algorithm to "MessageSigner" and decoupled it from "Session"
Changed flags and byte order for signature generation
Updated the logic for setting the security mode and server capabilities during negotiation
Added logic for triggering signing and selecting signing keys
Added missing update operations for "Session.preauthIntegrityHashValue"
Added missing 0-bytes in labels
Added dependency on bouncy castle
Added test case

This is required for integrations tests using SMBJ because the library only supports CMAC.
See: https://github.com/cryptomator/jsmb/blob/132cba90a026a6f2b40da91059a4ff683ac3c1ab/src/test/java/org/cryptomator/jsmb/ConnectIT.java
This is required for recreating packets originally sent by Samba.
@JaniruTEC
Copy link
Contributor Author

Methodology for integration tests:
TBA

Copy link

coderabbitai bot commented Oct 12, 2025

Walkthrough

The diff adds SMB2 message signing (AES-CMAC/AES-GMAC) including MessageSigner, signing capabilities enum, and signing helpers on SMB2Message; introduces LOGOFF types and a Runtime.logoff handler; extends negotiation and session-setup to derive keys via a NIST SP800-108 HMAC-SHA256 KDF and to manage signing/encryption flags and multi-channel checks; integrates signing key generation into NTLMv2/NtlmSession (new Authenticated record) and updates TcpConnection to sign responses. It adds BouncyCastle and Guava dependencies, module require for BouncyCastle, SRVS session tracking, multiple session/header helpers, and new unit tests for signer and KDF.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive The pull request lacks any descriptive text, leaving reviewers without context for the extensive message signing and cryptographic updates, so the description does not convey meaningful information about the changes. Please add a descriptive summary of the key changes, such as the implementation of message signing across modules, updates to cryptographic utilities, and any newly introduced public APIs, to aid reviewers in understanding the pull request.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately summarizes the primary change in this pull request, which centers on implementing message signing functionality across the codebase. It is concise, specific, and directly reflects the main feature introduced without extraneous details.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/message-signing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
src/main/java/org/cryptomator/jsmb/smb2/negotiate/SigningCapabilities.java (1)

12-32: Expose an enum-friendly builder overload

We now have a strongly-typed Algorithm, but build still forces callers back to raw chars. Adding an overload keeps the API cohesive and removes the need for every caller to sprinkle getValue() conversions.

 	public static SigningCapabilities build(char algId) {
 		var data = MemorySegment.ofArray(new byte[4]);
 		data.set(Layouts.LE_UINT16, 0, (char) 1); // signing algorithm count
 		data.set(Layouts.LE_UINT16, 2, algId); // first element in algorithm list
 		return new SigningCapabilities(data);
 	}
+
+	public static SigningCapabilities build(Algorithm alg) {
+		return build(alg.getValue());
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 132cba9 and 763c1d6.

📒 Files selected for processing (27)
  • .idea/runConfigurations/NtlmSessionTest.xml (0 hunks)
  • pom.xml (3 hunks)
  • src/main/java/module-info.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/TcpConnection.java (4 hunks)
  • src/main/java/org/cryptomator/jsmb/common/NTStatus.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/ntlmv2/Authenticator.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/ntlmv2/Crypto.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/ntlmv2/NtlmSession.java (2 hunks)
  • src/main/java/org/cryptomator/jsmb/smb1/SMB1Negotiator.java (3 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/Connection.java (2 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/Global.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/LogoffRequest.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/LogoffResponse.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/Negotiator.java (9 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/PacketHeader.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/PacketHeaderBuilder.java (2 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/Runtime.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/SMB2Message.java (3 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/SMB2MessageParser.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/Session.java (5 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/crypto/MessageSigner.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/crypto/NistSP800108KDF.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/smb2/negotiate/SigningCapabilities.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/srvs/SrvsGlobal.java (1 hunks)
  • src/main/java/org/cryptomator/jsmb/srvs/SrvsSession.java (1 hunks)
  • src/test/java/org/cryptomator/jsmb/smb2/crypto/MessageSignerTest.java (1 hunks)
  • src/test/java/org/cryptomator/jsmb/smb2/crypto/NistSP800108KDFTest.java (1 hunks)
💤 Files with no reviewable changes (1)
  • .idea/runConfigurations/NtlmSessionTest.xml
🧰 Additional context used
🧬 Code graph analysis (9)
src/main/java/org/cryptomator/jsmb/smb1/SMB1Negotiator.java (1)
src/main/java/org/cryptomator/jsmb/smb2/Connection.java (1)
  • Connection (14-45)
src/main/java/org/cryptomator/jsmb/TcpConnection.java (3)
src/main/java/org/cryptomator/jsmb/smb2/Connection.java (1)
  • Connection (14-45)
src/main/java/org/cryptomator/jsmb/smb2/SMB2MessageParser.java (1)
  • SMB2MessageParser (8-33)
src/main/java/org/cryptomator/jsmb/smb2/Session.java (1)
  • Session (20-83)
src/main/java/org/cryptomator/jsmb/smb2/Session.java (1)
src/main/java/org/cryptomator/jsmb/srvs/SrvsGlobal.java (1)
  • SrvsGlobal (11-22)
src/test/java/org/cryptomator/jsmb/smb2/crypto/NistSP800108KDFTest.java (1)
src/main/java/org/cryptomator/jsmb/smb2/crypto/NistSP800108KDF.java (1)
  • NistSP800108KDF (20-64)
src/main/java/org/cryptomator/jsmb/smb2/Negotiator.java (3)
src/main/java/org/cryptomator/jsmb/smb2/crypto/NistSP800108KDF.java (1)
  • NistSP800108KDF (20-64)
src/main/java/org/cryptomator/jsmb/smb2/Session.java (1)
  • Session (20-83)
src/main/java/org/cryptomator/jsmb/util/Bytes.java (1)
  • Bytes (3-28)
src/main/java/org/cryptomator/jsmb/smb2/crypto/NistSP800108KDF.java (1)
src/main/java/org/cryptomator/jsmb/util/Bytes.java (1)
  • Bytes (3-28)
src/main/java/org/cryptomator/jsmb/smb2/crypto/MessageSigner.java (2)
src/main/java/org/cryptomator/jsmb/smb2/Connection.java (1)
  • Connection (14-45)
src/main/java/org/cryptomator/jsmb/util/Bytes.java (1)
  • Bytes (3-28)
src/main/java/org/cryptomator/jsmb/smb2/SMB2Message.java (2)
src/main/java/org/cryptomator/jsmb/smb2/crypto/MessageSigner.java (1)
  • MessageSigner (34-109)
src/main/java/org/cryptomator/jsmb/util/Bytes.java (1)
  • Bytes (3-28)
src/main/java/org/cryptomator/jsmb/ntlmv2/NtlmSession.java (2)
src/main/java/org/cryptomator/jsmb/ntlmv2/Crypto.java (1)
  • Crypto (13-64)
src/main/java/org/cryptomator/jsmb/util/Bytes.java (1)
  • Bytes (3-28)
🪛 ast-grep (0.39.6)
src/main/java/org/cryptomator/jsmb/ntlmv2/Crypto.java

[warning] 28-28: Detected MD5 hash algorithm which is considered insecure. MD5 is not collision resistant and is therefore not suitable as a cryptographic signature. Use HMAC instead.
Context: "MD5"
Note: [CWE-328] Use of Weak Hash. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(use-of-md5-java)

🔇 Additional comments (8)
src/main/java/org/cryptomator/jsmb/common/NTStatus.java (1)

13-13: LGTM!

The new status constant is correctly defined and appropriately placed. The value matches the NT status code specification.

src/main/java/module-info.java (1)

4-4: LGTM!

The BouncyCastle provider dependency is appropriate for implementing SMB2 message signing with AES-CMAC/AES-GMAC support. Based on learnings, version 1.80 provides robust cryptographic primitives for this use case.

src/main/java/org/cryptomator/jsmb/smb2/PacketHeaderBuilder.java (2)

18-21: LGTM!

The copy constructor correctly initializes a mutable builder from an existing header. This pattern enables fluent header modification for signing operations.


74-74: LGTM!

The signature offset at 48 is correct per the SMB2 packet header specification (16-byte signature field begins after the 8-byte SessionId at offset 40).

src/main/java/org/cryptomator/jsmb/ntlmv2/Crypto.java (1)

27-34: LGTM!

The MD5 implementation is correct and follows the established pattern. The static analysis warning about weak hashing can be safely ignored here—MD5 is required by the NTLMv2 authentication protocol for HMAC-MD5 operations, not for general cryptographic signing.

src/main/java/org/cryptomator/jsmb/srvs/SrvsSession.java (1)

8-11: LGTM!

The record definition is clean and the AtomicInteger ensures thread-safe session ID generation across the full integer range.

src/test/java/org/cryptomator/jsmb/smb2/crypto/NistSP800108KDFTest.java (1)

9-29: LGTM!

The test implementation is thorough and uses official NIST CAVS 14.4 test vectors for HMAC-SHA256 KDF validation. The parameterized approach provides good coverage across different key lengths and output sizes.

src/main/java/org/cryptomator/jsmb/smb1/SMB1Negotiator.java (1)

48-48: LGTM!

The conditional security mode computation correctly enforces signing requirements based on the global configuration. The bitwise OR operation appropriately combines the SIGNING_ENABLED and conditional SIGNING_REQUIRED flags.

Comment on lines 34 to 38
public byte[] preauthIntegrityHashValue = new byte[64];
public char cipherId;
public char signingAlgorithmId;
public SigningCapabilities.Algorithm signingAlgorithmId;
public char[] compressionIds;
public boolean supportsChainedCompression;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restore the default signing algorithm

Changing signingAlgorithmId from char to Algorithm removes the implicit default of 0x0000 (HMAC-SHA256). In SMB 2.x dialects the server never sends SIGNING_CAPABILITIES, so this field now stays null and any later getValue()/switch will blow up. Initialize it to Algorithm.HMAC_SHA256 to preserve the previous behavior.

-	public SigningCapabilities.Algorithm signingAlgorithmId;
+	public SigningCapabilities.Algorithm signingAlgorithmId = SigningCapabilities.Algorithm.HMAC_SHA256;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public byte[] preauthIntegrityHashValue = new byte[64];
public char cipherId;
public char signingAlgorithmId;
public SigningCapabilities.Algorithm signingAlgorithmId;
public char[] compressionIds;
public boolean supportsChainedCompression;
public byte[] preauthIntegrityHashValue = new byte[64];
public char cipherId;
public SigningCapabilities.Algorithm signingAlgorithmId = SigningCapabilities.Algorithm.HMAC_SHA256;
public char[] compressionIds;
public boolean supportsChainedCompression;
🤖 Prompt for AI Agents
In src/main/java/org/cryptomator/jsmb/smb2/Connection.java around lines 34 to
38, the signingAlgorithmId field was changed from a primitive char to
SigningCapabilities.Algorithm and thus can be null when the server omits
SIGNING_CAPABILITIES; restore the implicit default by initializing
signingAlgorithmId to SigningCapabilities.Algorithm.HMAC_SHA256 in its
declaration so it defaults to HMAC-SHA256 (0x0000) and avoids NPEs in later
getValue()/switch usage.

Comment on lines +100 to +103
Mac mac = Mac.getInstance("AESCMAC", bcProvider);
mac.init(new SecretKeySpec(signingKeyBytes, "AES128"));
return mac.doFinal(data);
} catch (NoSuchAlgorithmException e) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix SecretKeySpec algorithm to prevent signing failure

SecretKeySpec must be created with algorithm "AES" for BouncyCastle's AESCMAC. Using "AES128" causes InvalidKeyException, so every CMAC signing attempt will fail at runtime.

-           mac.init(new SecretKeySpec(signingKeyBytes, "AES128"));
+           mac.init(new SecretKeySpec(signingKeyBytes, "AES"));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Mac mac = Mac.getInstance("AESCMAC", bcProvider);
mac.init(new SecretKeySpec(signingKeyBytes, "AES128"));
return mac.doFinal(data);
} catch (NoSuchAlgorithmException e) {
Mac mac = Mac.getInstance("AESCMAC", bcProvider);
mac.init(new SecretKeySpec(signingKeyBytes, "AES"));
return mac.doFinal(data);
} catch (NoSuchAlgorithmException e) {
🤖 Prompt for AI Agents
In src/main/java/org/cryptomator/jsmb/smb2/crypto/MessageSigner.java around
lines 100 to 103, the SecretKeySpec is constructed with algorithm "AES128" which
causes InvalidKeyException when used with BouncyCastle's AESCMAC; change the
SecretKeySpec algorithm to "AES" so the key is recognized correctly, keep using
the same signingKeyBytes and initialization flow, and ensure imports/exceptions
remain unchanged.

* @see <a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-srvs/cb311421-de4d-4cd7-bb05-ce52e03814e4">Inserting an entry</a>
* @see <a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-srvs/d5a84302-764b-4d19-942c-4ec6eacc2703">Removing an entry</a>
*/
public final Map<Integer, SrvsSession> sessionList = new HashMap<>(); //Map<GlobalSessionId,SrvsSession> instead of List to make lookup easier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Thread-safety issue: Use ConcurrentHashMap for sessionList.

The sessionList HashMap is accessed by multiple SMB connections concurrently without synchronization, leading to potential race conditions and data corruption. SMB servers handle concurrent connections, so shared mutable state requires thread-safe data structures.

Apply this diff:

-	public final Map<Integer, SrvsSession> sessionList = new HashMap<>(); //Map<GlobalSessionId,SrvsSession> instead of List to make lookup easier
+	public final Map<Integer, SrvsSession> sessionList = new ConcurrentHashMap<>(); //Map<GlobalSessionId,SrvsSession> instead of List to make lookup easier

And add the import:

import java.util.concurrent.ConcurrentHashMap;
🤖 Prompt for AI Agents
In src/main/java/org/cryptomator/jsmb/srvs/SrvsGlobal.java around line 20, the
mutable sessionList is declared as a plain HashMap which is not thread-safe;
replace the HashMap instantiation with a ConcurrentHashMap while keeping the
field type as Map<Integer, SrvsSession>, and add the import statement import
java.util.concurrent.ConcurrentHashMap; so concurrent access by multiple SMB
connections is safe without additional synchronization.

The tests are based on packet captures obtained from communication between two Samba instances.
See: #2 (comment)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/test/java/org/cryptomator/jsmb/smb2/crypto/MessageSignerTest.java (1)

75-115: Consider adding documentation for the CSV parameters and negative test cases.

The parameterized test structure is solid and covers multiple signing algorithms. However, the CSV data is dense and would benefit from inline comments explaining each field (sessionId, expUnsignedHdrHex, securityBufferHex, etc.).

Additionally, consider adding negative test cases as future enhancements, such as:

  • Signing with an incorrect key length
  • Verifying signature mismatch detection
  • Handling null or empty inputs

These additions would improve test coverage but are not blocking for the current implementation.

Example documentation approach:

@ParameterizedTest
@CsvSource(textBlock = """
    # sessionId, expUnsignedHdrHex, securityBufferHex, expUnsignedMsgHex, signingKeyHex, signingAlg, expSignedMsgHex
    0x00000000ef9270f6,\
    fe534d42...,\
    ...
    """)
public void testSigning(
    long sessionId,
    String expUnsignedHdrHex,
    String securityBufferHex,
    // ... rest of parameters
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 763c1d6 and c2c864b.

📒 Files selected for processing (1)
  • src/test/java/org/cryptomator/jsmb/smb2/crypto/MessageSignerTest.java (1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.28.0)
src/test/java/org/cryptomator/jsmb/smb2/crypto/MessageSignerTest.java

[high] 43-43: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (2)
src/test/java/org/cryptomator/jsmb/smb2/crypto/MessageSignerTest.java (2)

25-73: Excellent use of NIST test vectors.

The tests correctly validate GMAC and CMAC implementations using standard NIST test vectors, which is a best practice for cryptographic code. The nonce construction in testGmacSignature is correct, and the test documentation provides proper traceability to the source vectors.

Regarding the static analysis hint on line 43: This is a false positive. The flagged string is a published NIST CAVP test vector, not a real API key or secret.


117-123: Clean helper implementations.

The SignedMessage record and assertBytesEquals helper method are well-designed. Using hex formatting in the assertion provides clear, readable error messages when tests fail.

Copy link
Member

@overheadhunter overheadhunter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work! Just tested it on macOS with Finder and Cyberduck and the Wireshark dumps look good.

Just some minor remarks.

Comment on lines +35 to +37
} else {
throw new UnsupportedOperationException("Not yet implemented");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else branch should be trivial:

     Else
       Set ChallengeFromClient to NIL
     EndIf

Comment on lines -82 to +87
connection.serverCapabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
if (connection.cipherId != EncryptionCapabilities.NO_COMMON_CIPHER) {
connection.serverCapabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you reference the specs in a comment? I don't understand why not agreeing on a common cipher requires us to "lie" about our capabilities.

module org.cryptomator.jsmb {
requires org.slf4j;
requires static org.jetbrains.annotations;
requires org.bouncycastle.provider;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok for now, but we should try to add a cmac impl without requiring further dependencies

var signature = signer.gmac(data, nonce, Base64.getDecoder().decode("pIURFzKKk79Sg4LyLzWslGiCWf0vUX5P0n7pz5uMiiw="));

Assertions.assertEquals("GxPmEyQV/XDZCS4y/ydZvg==", Base64.getEncoder().encodeToString(signature));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove blank line

Comment on lines +100 to +105
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.80</version>
<scope>compile</scope>
</dependency>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move above <!-- tests -->; add comment what this is used for (we aim to provide a zero-dependency lib)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants