-
Notifications
You must be signed in to change notification settings - Fork 1
Implement message signing #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
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
This allows handling of null values
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 integrations tests using SMBJ because the functionality is expected by the library. 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.
Methodology for integration tests: |
WalkthroughThe 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)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 overloadWe now have a strongly-typed
Algorithm
, butbuild
still forces callers back to raw chars. Adding an overload keeps the API cohesive and removes the need for every caller to sprinklegetValue()
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
📒 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.
public byte[] preauthIntegrityHashValue = new byte[64]; | ||
public char cipherId; | ||
public char signingAlgorithmId; | ||
public SigningCapabilities.Algorithm signingAlgorithmId; | ||
public char[] compressionIds; | ||
public boolean supportsChainedCompression; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
Mac mac = Mac.getInstance("AESCMAC", bcProvider); | ||
mac.init(new SecretKeySpec(signingKeyBytes, "AES128")); | ||
return mac.doFinal(data); | ||
} catch (NoSuchAlgorithmException e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
There was a problem hiding this 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
📒 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 andassertBytesEquals
helper method are well-designed. Using hex formatting in the assertion provides clear, readable error messages when tests fail.
There was a problem hiding this 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.
} else { | ||
throw new UnsupportedOperationException("Not yet implemented"); | ||
} |
There was a problem hiding this comment.
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
connection.serverCapabilities |= SMB2_GLOBAL_CAP_ENCRYPTION; | ||
if (connection.cipherId != EncryptionCapabilities.NO_COMMON_CIPHER) { | ||
connection.serverCapabilities |= SMB2_GLOBAL_CAP_ENCRYPTION; | ||
} |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove blank line
<dependency> | ||
<groupId>org.bouncycastle</groupId> | ||
<artifactId>bcprov-jdk18on</artifactId> | ||
<version>1.80</version> | ||
<scope>compile</scope> | ||
</dependency> |
There was a problem hiding this comment.
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)
No description provided.